完善中,后面会不间断更新。
一、Vue 3.0六大亮点
1、performance:性能比vue2.x快1.2-2倍。
2、Treeshaking support:按需编译,体积比vue2.x更小。
3、Composition API(类似react Hooks)。
4.Better TypeScript support:更好的TS支持。
5、Custom Renderer API:暴露了自定义API。
6、Fragment,Teleport(Protal),Supense:更先进的组件。
二、Vue3.0是如何变化更快的?
difff算法优化:
Vue2中的虚拟dom是进行全量的对比。
Vue3新增了静态标记(PatchFlag)。
在与上次虚拟节点进行对比的时候,只对比带有patch flag的节点
并且可以通过flag的信息得知当前节点对比的具体内容。
三、 静态提升
1、vue2中无论与元素是否参与更新,每次都会重新创建,然后在渲染。
3、vue3中对于不参与更新的元素,会做静态提升你如果,只会被创建一次,在渲染时直接复用即可。
四、事件监听缓存
默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化,但因为时同一个函数,所以没追踪到变化,会直接缓存起来复用。
五、vue3.0如何在组合API(Composition API)中定义方法定义变量
1、setup函数是 Composition API(组合API/注入API)的入口。
2、setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间来执行的。
注:在setup方法中定义的变量或者方法必须return才能在视图中使用,ref用来监听简单类型的数据变化,不能监听复杂类型的数据变化
demo-1
在setup方法中定义个变量count并在视图中渲染这个变量,点击按钮count加一,视图随之更新。
demo-2-1
声名一个变量,并将数据渲染到页面,点击那条数据,就删除掉页面对应的数据。
注:如果数据时一个复杂类型那么就需要使用reactive来监听数据的变化。
demo-2-2,解决vue2.x数据业务逻辑分散的问题
同demo-2-1的功能换一种写法。
这样写解决了vue2.x数据业务逻辑分散的问题,现在的数据和逻辑就整合在一起就不分散了。
demo3,将删除功能和新增功能进行封装使用。
看了下面这种写法后觉得确实很强,将页面的业务逻辑都放在了一块,逻辑不在分散,下面只是初步封装,当然你的项目有很多业务逻辑的话,你也可以拿出来单独进行封装。
<template>
<div>
<form>
<input type="text" v-model="state2.obj.id">
<input type="text" v-model="state2.obj.name">
<input type="text" v-model="state2.obj.age">
<input type="submit" @click="addObj">
</form>
<ul>
<li v-for="(v,i) in state.obj" :key="i" @click="remObj(i)">
{{v.name}}---{{v.age}}
</li>
</ul>
</div>
</template>
<script>
import {reactive} from "vue"
export default {
name: 'App',
setup(){
let {state,remObj}=useRemoveObj()
let {state2,addObj}=useAddObj(state)
return {state,remObj,state2,addObj}
}
}
// 删除数据
function useRemoveObj(){
let state=reactive({
obj:[
{id:1,name:"guo-1",age:10},
{id:2,name:"guo-2",age:20},
{id:3,name:"guo-3",age:30},
]
})
function remObj(i){
state.obj=state.obj.filter((item,index)=>index!==i)
}
return {state,remObj}
}
// 新增数据
function useAddObj(state){
let state2=reactive({
obj:{
id:"",
name:"",
age:""
}
})
function addObj(e){
e.preventDefault()
const stu = Object.assign({},state2.obj)
state.obj.push(stu)
state2.obj.id=""
state2.obj.name=""
state2.obj.age=""
}
return {state2,addObj}
}
</script>
六、setup梳理及注意点
1、setup:执行时机
- setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数。
- beforeCreate:表示组件刚刚被创建出来,组件的data和methods还没有初始化好
- Created:表示组件刚刚被创建出来,组件的data和methods已经初始化好了
2、setup注意点
- 由于执行setup函数的时候,还没有执行Created生命周期方法,所以setup函数中,无法使用data和methods
- 由于不能在setup函数中使用data和methods,所以vue为了避免我们错误使用,他将setup函数中的this修改成了undefined。
- setup函数只能是同步的,不能是异步的。
七、什么是reactive
1、reactive是Vue3中提供的实现响应式数据的方法。
2、在Vue2中响应式数据是通过defineProperty实现的,而在Vue3中响应式数据是通过ES6的Proxy来实现的。
reactive注意点
1、reactive参数必须是对象(json/arr)
2、如果给reactive传递了其他对象
例如日期对象
- 默认情况下修改对象,界面不会更新。
- 如果想更新,可以通过重新赋值的方式。
例1:传递一个Number类型
<template>
<div>
{{state}}
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive} from "vue"
export default {
name: 'App',
setup(){
// 创建一个响应式数据
// 本质:就是将传入的数据包装成一个Proxy对象
let state=reactive(123)
function myFn(){
state=666
console.log("state",state)//666
}
return {state,myFn}
}
}
</script>
通过以上代码可以发现,reactive如果传递的是一个简单类型的数据,数据是可以在页面中渲染出来的,但是当我们修改数据之后,页面不会更新,这个时候就需要使用ref,如下案例。
<template>
<div>
{{state}}
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive,ref} from "vue"
export default {
name: 'App',
setup(){
let state=ref(123)
function myFn(){
state.value=666
console.log("state",state)//666
}
return {state,myFn}
}
}
</script>
如上可以发现,如果是一个简单数据类型,那么我们使用ref,那么就可以监听到数据的变化,如果json或者arr就用reactive,就可以监听到数据变化。
例2——如果给reactive传的是一个日期对象,数据修改页面不会刷新问题。
<template>
<div>
{{state.time}}
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive,ref} from "vue"
export default {
name: 'App',
setup(){
let state=reactive({
time:new Date()
})
function myFn(){
state.time.setDate(state.time.getDate()+1)
console.log("state",state)
}
return {state,myFn}
}
}
</script>
如上代码,我们给reactive传递了一个日期对象,但当我们修改日期对象的时候可以发现,数据已经修改了,但是页面不会刷新。 上面也总结过,如果reactive传递了json和arr以外的其他对象,修改数据视图是不会更新的,想要更新可以通过重新赋值的方式来实现
。
例3——如下,如果reactive传递的是如期对象
如下代码可以发现,直接修改日期对象视图是不会刷新的。
<template>
<div>
{{state.time}}
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive,ref} from "vue"
export default {
name: 'App',
setup(){
let state=reactive({
time:new Date()
})
function myFn(){
state.time.setDate(state.time.getDate()+1)
console.log("state",state.time)
}
return {state,myFn}
}
}
</script>
按照如下方法重新赋值之后,可以发现视图会随着数据的变化而更新。
<template>
<div>
{{state.time}}
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive,ref} from "vue"
export default {
name: 'App',
setup(){
let state=reactive({
time:new Date()
})
function myFn(){
const newTime=new Date(state.time.getTime())
newTime.setDate(state.time.getDate()+1)
state.time=newTime
console.log("state",state.time)
}
return {state,myFn}
}
}
</script>
八、什么是ref
- ref和reactive一样,也是用来实现响应式数据的方法。
- 由于reactive必须传递一个对象所以导致在企业开发中,如果我们只想让某个变量实现响应式的时候会非常麻烦,所以Vue3就给我们提供了ref方法,实现对简单类型数据的监听。
ref本质
- ref底层的本质其实还reactive系统会自动根据我们给ref传入的值将它转换成ref(xx)—>reactive({value:xx})
ref注意点
- 在Vue中使用ref的值不用通过value获取
- 在JS中使用ref必须通过value获取
ref和reactive的区别
- 如果在template里使用的是ref类型的数据,那么Vue会自动帮我们添加。value。
- 如果在template里使用reactive类型的数据,那么Vue不会帮我们添加.value
Vue如何决定是否需要添加.value的
- Vue在解析数据之前,会自动判断这个数据是否是ref类型的,如果是就自动添加.value,如果不是就不添加。
Vue如何判断当前数据是否为ref类型
- 通过当前数据的__v_isRef属性来判断的,有这个私有属性,并且值为true,那么就代表是ref类型的数据
九、如何判断数据是ref还是reactive类型的数据。
- 判断数据类型为ref使用isRef方法,返回true则为真,反之为假。
- 判断数据类型为reactive使用isReactive方法,返回true则为真,反之为假。
Demo
<template>
<div>
{{state.time}}
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive,ref,isRef,isReactive} from "vue"
export default {
name: 'App',
setup(){
let state=reactive({
time:new Date()
})
let age=ref(10)
function myFn(){
state.time.setDate(state.time.getDate()+1)
console.log("state",isRef(state),isReactive(state))//state false true
console.log("age",isRef(age),isReactive(age))//age true false
}
return {state,myFn}
}
}
</script>
十、递归监听
递归监听就是不管你数据嵌套多少层它都能够监听到。
- 默认情况下无论是ref还是reactive都是
递归监听
递归监听存在的问题
不论是使用ref还reactive,他们内部都会做一个递归,将每层数据都包装成proxy对象,既然他要将每层数据都通过递归包装成proxy对象,那么内部是要做很多事的,所以他比较消耗能的。
Demo
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive,ref,isRef,isReactive} from "vue"
export default {
name: 'App',
setup(){
let state=reactive({
a:"a",
gf:{
b:"b",
f:{
c:"c",
s:{
d:"d"
}
}
}
})
function myFn(){
console.log(state)
console.log(state.gf)
console.log(state.gf.f)
// 由打印结果可以看出来不论是使用ref还reactive,他们内部都会做一个递归,将每层数据都包装成proxy对象,既然他要将每层数据都通过递归包装成proxy对象,那么内部是要做很多事的,所以他比较消耗能的。
state.a=1
state.gf.b=2
state.gf.f.c=3
state.gf.f.s.d=4
// state.value.a=1
// state.value.gf.b=2
// state.value.gf.f.c=3
// state.value.gf.f.s.d=4
}
return {state,myFn}
}
}
</script>
十一、非递归监听
- 非递归监听只能监听第一层
shallowReactive
和shallowRef
分别类似于reactive
和ref
,但是shallowReactive
和shallowRef
是非递归监听,reactive
和ref
是递归监听。
十二、toRaw
ref/reactive数据类型特点
每次修改都会被追踪到,都会更新视图,但是这样其实非常消耗性能,所以如果我们有一些操作不需要追踪,不需要视图刷新,那么这个时候我们就可以通过toRaw
方法拿到它的原始数据,对原始数据进行修改这样就不会被追踪,这样视图就不会刷新,这样就第一程度上防止了性能的消耗
。
十三、markRaw
markRaw
:让某一条数据永远都不被追踪。
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive,markRaw} from "vue"
export default {
name: 'App',
setup(){
let obj={name:"guo123",age:18}
obj=markRaw(obj)
let state=reactive(obj)
function myFn(){
state.name="guodong"
// 再次可以发现打印结果变了,但是视图没有刷新,
//markRaw的作用就是让这是数据不要被追踪
console.log(obj.name)//guodong
}
return {state,myFn}
}
}
</script>
十四、toRef和ref的区别
- ref是值的复制,修改响应式数据不会影响原始数据,数据变化视图更新。
- toRef是值的引用,修改响应式数据会影响原始数据,数据变化视图不更新。
toRef应用场景
如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后视图不需要更新,那么就可以使用toRef。
例——ref
由下面案例打印结果可以看出来,用ref将某一个对象中的属性变成响应式的数据,我们修改响应式数据是不会影响到原始数据的,页面随数据的变化而更新。
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {ref} from "vue"
export default {
name: 'App',
setup(){
let obj={name:"guo123"}
let state=ref(obj)
console.log("state",state)
function myFn(){
state.value.name="guodong"
console.log("obj",obj.name)//guodong
console.log("state",state.value.name)//guodong
}
return {state,myFn}
}
}
</script>
例二——toRef
如果利用toRef将某一个对象中的属性变成响应式的数据,我们修改响应式的数据是会影响到原始数据,但是响应式数据是通过toRef创建的,那么修改了数据并不会触发视图更新
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {toRef} from "vue"
export default {
name: 'App',
setup(){
let obj={name:"guo123"}
// let state=ref(obj)
let state=toRef(obj,'name')
console.log("state",state.value)
function myFn(){
state.value="guodong"
// 打印结果可以看出来,数据变了但视图没有更新。
console.log("obj",obj.name)//guodong
console.log("state",state.value)//guodong
}
return {state,myFn}
}
}
</script>
十五、toRefs
如果我们由多个数据的修改不希望被追踪到,需要将没一个值传递给toRef,显然这样做比较麻烦,这个时候就需要使用toRefs,可以直接传递一个对象给他。
Demo
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {toRefs} from "vue"
export default {
name: 'App',
setup(){
let obj={name:"guo123",age:18}
let state=toRefs(obj)
function myFn(){
state.name.value="guodong"
state.age.value=20
console.log(state)
}
return {state,myFn}
}
}
</script>
十六、customRef——自定义ref方法
customRef方法接收一个回调函数,在回调函数中必须返回一个对象,在这个对象里面还由get和set这两个方法。
Demo
<template>
<div>
<p>{{age}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {customRef} from "vue"
function myRef(value){
return customRef((track, trigger)=>{
return {
get(){
console.log("get",value)
track();//track方法作用是告诉Vue这个数据是需要追踪变化的
return value
},
set(newValue){
console.log("set",newValue)
value=newValue
trigger();//告诉Vue触发界面更新
}
}
})
}
export default {
name: 'App',
setup(){
let age=myRef(18)
function myFn(){
age.value+=1
}
return {age,myFn}
}
}
</script>
十七、customRef中请求异步数据
setup函数只能是同步的,所以异步请求放在setup函数中是不行的,这个时候就需要用到customRef。
<template>
<div>
<ul>
<li v-for="(v,i) in state" :key="i">{{v.name}}</li>
</ul>
</div>
</template>
<script>
import {customRef,ref} from "vue"
function myRef(value){
return customRef((track, trigger)=>{
//异步数据请求
fetch(value)
.then((res)=>{
return res.json()
})
.then((data)=>{
console.log("data",data)
value=data;
trigger();
})
.catch((err)=>{
console.log("err",err)
})
return {
get(){
console.log("get",value)
track();//track方法作用是告诉Vue这个数据是需要追踪变化的
return value
},
set(newValue){
console.log("set",newValue)
value=newValue
trigger();//告诉Vue触发界面更新
}
}
})
}
export default {
name: 'App',
setup(){
let state=myRef("../public/data.json")
return {state}
}
}
</script>
十八、Vue3中如何获取元素节点并在父组件中调用子组件方法
父组件
<template>
<div ref="box">
我是div
<Child ref="testRef" />
<div @click="handleTest">调用子组件方法</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import Child from './component/Child.vue';
let box = ref<HTMLElement>();
//此处是获取子组件dom 变量名testRef必须与Child组件上绑定的ref的值相同,不然获不到子组件的dom
let testRef = ref<InstanceType<typeof Child>>();
const handleTest = async () => {
testRef.value?.initData();
};
onMounted(() => {
console.log('testRef', testRef.value);
console.log('onMounted', box.value);
});
</script>
子组件
在 script-setup 模式下,如果要调用子组件的数据,需要先在子组件显式的暴露出来,才能够正确的拿到,这个操作,就是由 defineExpose API 来完成。
<template>
<div>
<h1>这是一个子组件</h1>
</div>
</template>
<script setup lang="ts">
// 第一步:子组件中声明方法
const initData = async () => {
console.log('父组件中调用了该方法');
};
// 第二步 重要 :使用 defineExpose 声明父组件要调用的方法
defineExpose({
initData,
});
</script>
十九、readonly
readonly
:用于创建一个只读数据,并且是递归只读
通过下面案例可以发现,当点击按钮后修改数据是无效的。
<template>
<div>
<p>{{state.name}}</p>
<p>{{state.attr.age}}</p>
<p>{{state.attr.height}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {readonly} from "vue"
export default {
name: 'App',
setup(){
let state=readonly({name:"guo",attr:{age:18,height:1.88}})
function myFn(){
state.name="张三"
state.attr.age=18
state.attr.height=1.80
console.log(state)
}
return {state,myFn}
}
}
</script>
二十、shallowReadonly
用于创建一个只读数据,但是不是递归读的
<template>
<div>
<p>{{state.name}}</p>
<p>{{state.attr.age}}</p>
<p>{{state.attr.height}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {readonly,shallowReadonly} from "vue"
export default {
name: 'App',
setup(){
let state=shallowReadonly({name:"guo",attr:{age:18,height:1.88}})
function myFn(){
state.name="张三"
state.attr.age=666
state.attr.height=111
console.log(state)
}
return {state,myFn}
}
}
</script>
二十一、isReadonly
- 检查对象是否是由
readonly
或shallowReadonly
创建的只读数据,返回ture为真。
<template>
<div>
<p>{{state.name}}</p>
<p>{{state.attr.age}}</p>
<p>{{state.attr.height}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {readonly,shallowReadonly,isReadonly} from "vue"
export default {
name: 'App',
setup(){
let state=shallowReadonly({name:"guo",attr:{age:18,height:1.88}})
function myFn(){
console.log(isReadonly(state))
}
return {state,myFn}
}
}
</script>
二十二、子组件中调用父组件方法
父组件
<template>
<div ref="box"
>我是div
<Child ref="testRef" @handleSon="handleSon" />
</div>
</template>
<script setup lang="ts">
import Child from './component/Child.vue';
const handleSon = () => {
console.log('这是子组件调用了父级的方法');
};
</script>
子组件
<template>
<div>
<h1>这是一个子组件</h1>
<div @click="handleFather">调用父组件方法</div>
</div>
</template>
<script setup lang="ts">
import { defineEmits } from 'vue';
const emit = defineEmits(['handleSon']);
const handleFather = () => {
emit('handleSon');
};
</script>
二十三、插槽使用
父组件
<template>
<div ref="box">
我是父亲
<!-- 子组件 -->
<Child ref="testRef">
<!-- 匿名插槽 -->
<p>这是匿名插槽的内容</p>
<!-- 匿名插槽 -->
<!-- 具名插槽 -->
<template #msg>
<p>这是具名插槽</p>
</template>
<!-- 具名插槽 -->
</Child>
<!-- 子组件 -->
</div>
</template>
<script setup lang="ts">
import Child from './component/Child.vue';
</script>
子组件
<template>
<div class="Child">
<h1>我是儿子</h1>
<!-- 匿名插槽 -->
<slot></slot>
<!-- 匿名插槽 -->
<!-- 具名插槽 -->
<slot name="msg"></slot>
<!-- 具名插槽 -->
</div>
</template>
<script setup lang="ts"></script>
二十三、顶级 await 的支持
在 script-setup 模式下,不必再配合 async 就可以直接使用 await 了,这种情况下,组件的 setup 会自动变成 async setup 。
<script setup lang="ts">
const res = await fetch(`https://example.com/api/foo`)
const json = await res.json()
console.log(json)
</script>
它转换成标准组件的写法就是:
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
async setup() {
const res = await fetch(`https://example.com/api/foo`)
const json = await res.json()
console.log(json)
return {
json,
}
},
})
</script>