1、优化点
相较vue2.0,vue3.0主要有以下变化
1、性能优化
打包体积减少约40%,首次渲染速度减少50%,更新渲染速度减少130%,内存占用减少约50%
2、使用proxy代替defineProperty实现响应式
3、更好的支持ts
4、优化生命周期,增加setup,ref,reactive等新的概念
2、composition API
1、setup
组件中所用到的:数据、方法等等,均要配置在setup中。
setup函数的两种返回值:
若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。
若返回一个渲染函数:则可以自定义渲染内容。
2、ref,reactive
都能实现响应式,但是ref操作时需要通过.value,略微复杂;注意reactive不能实现基础数据类型的响应式,会报错。ref对于基础类型的响应式使用getter和setter实现,复杂类型会调用reactive实现
<template>
<div>
{{ author }}
</div>
<el-button @click="changeAuthor">测试</el-button>
<div>
{{ author1.name }}
{{ author1.hobbies }}
{{ author1.parent }}
</div>
<el-button @click="changeAuthor1">测试</el-button>
<div>
{{ person.name }}
{{ person.hobbies }}
{{ person.parent }}
</div>
<el-button @click="changePerson">测试</el-button>
<div>
{{ test1 }}
</div>
<el-button @click="changeTest">测试</el-button>
</template>
<script>
import { reactive, ref } from "vue";
export default {
name: "List",
components: {},
setup(props) {
//直接定义无法实现响应式
// let author = 'a1'
// function changeAuthor(){
// console.log(1111)
// author = 'a2'
// }
// return {author,changeAuthor}
//使用ref定义基础类型实现响应式
let author = ref("a1");
function changeAuthor() {
author.value = "a2";
}
//使用ref实现对象、数组响应式
let author1 = ref({
name: "a",
age: 21,
hobbies: ["swimming", "running"],
parent: {
mom: "a1",
fath: "a2",
},
});
function changeAuthor1() {
author1.value.name = "b";
author1.value.hobbies[0] = "pingpang";
author1.value.parent.mom = "b1";
}
//使用reactive实现对象、数组响应式
let person = reactive({
name: "a",
hobbies: ["swimming", "running"],
parent: {
mom: "a1",
fath: "a2",
},
});
function changePerson() {
person.name = "b";
person.hobbies[0] = "pingpang";
person.parent.mom = "b1";
}
//注意,不能使用reactive定义基础类型,改变值时控制台会报错,如下图所示
let test1 = reactive("aaa");
function changeTest() {
test1 = "bbb";
}
return {
author,
changeAuthor,
person,
changePerson,
test1,
changeTest,
author1,
changeAuthor1,
};
},
};
</script>
<style>
</style>
使用reactive定义基础类型,改变值时控制台会报错
3、vue3中的响应式原理
使用proxy实现
let person = {
name: "a",
hobbies: ["swimming", "running"],
parent: {
mom: "a1",
fath: "a2",
},
}
let p = new Proxy(person, {
get(target, propName) {
return Reflect.get(target, propName);
},
set(target, propName, value) {
return Reflect.set(target, propName, value);
},
deleteProperty(target, propName) {
return Reflect.deleteProperty(target, propName);
},
});
4、watch,cumputed,watchEffect
//计算属性简写
// let hobbies = computed(() => {
// return person.hobbies.join(",");
// });
//计算属性完整写法
let hobbies = computed({
get() {
return person.hobbies.join(",");
},
set(value) {
person.hobbies = value.split(",");
},
});
//使用ref定义基础类型实现响应式
let author = ref("a1");
function changeAuthor() {
author.value = "a2";
}
//使用reactive实现对象、数组响应式
let person = reactive({
name: "a",
hobbies: ["swimming", "running"],
parent: {
mom: "a1",
fath: "a2",
},
});
function changePerson() {
person.name = "b";
person.hobbies[0] = "pingpang";
person.parent.mom = "b1";
}
watch(()=>author1.value.name,(newVal,oldVal)=>{
console.log(newVal)
console.log(oldVal)
})
watch(author,(newVal,oldVal)=>{
console.log(newVal)
console.log(oldVal)
})
//若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue,且强制开启了深度监视
watch(person,(newVal,oldVal)=>{
console.log(newVal)
console.log(oldVal)
})
//监视某个特定属性
watch(()=>person.name,(newVal,oldVal)=>{
console.log(newVal)
console.log(oldVal)
})
//不指定监听对象,回调函数中使用哪个变量则监听哪个变量,很方便,类似于computed,但无需返回值
watchEffect(() => {
let a = person.name
console.log('person.name更新了');
});
5、生命周期
与vue2相比,destoryed和beforeDestory没了,变成unmounted和beforeUnmount,setup在beforeCreate之前调用一次
beforeCreate==》setup
created==》setup
beforeMount==》onBeforeMount
mounted==》onMounted
beforeUpdate==》onBeforeUpdate
updated==》onUpdated
beforeDestory==》onBeforeUnmount
destoryed==》onUnmounted
<template>
<div>{{name}}</div>
<el-button @click="changeName"></el-button>
</template>
<script>
import { onBeforeMount, onMounted, onBeforeUnmount, onUnmounted,onBeforeUpdate,onUpdated,ref } from "vue";
export default {
name: "List",
components: {},
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate(){
console.log("beforeUpdate");
},
updated(){
console.log("updated");
},
// vue2中的 beforeDestroy与 destroyed已经改名 无法使用
beforeUnmount() {
console.log("vue2 中的生命周期 beforeDestroy(beforeUnmount)");
},
unmounted() {
console.log("vue2 中的生命周期 destroyed(unmounted)");
},
setup(props) {
let name = ref('a')
function changeName(){
name.value='b'
}
console.log("setup");
onBeforeMount(() => {
console.log("onBeforeMount");
});
onMounted(() => {
console.log("onMounted");
});
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
});
onUpdated(() => {
console.log("onUpdated");
});
onBeforeUnmount(() => {
console.log("onBeforeUnmount");
});
onUnmounted(() => {
console.log("onUnmounted");
});
return {name,changeName}
},
};
</script>
可以看出setup执行于beforeCreate之前,同类型的钩子函数,vue3中的先执行
6、hook
类似于vue2中的mixin的使用方式,可以将部分代码抽离封装,需要时引入使用,使用import引用
7、toRef,toRefs
toRef创建对象的引用,修改会影响原始值
// let person = reactive({
// name:'a',
// parents:{
// m:'a1',
// f:'a2'
// }
// })
let person = {
name:'a',
parents:{
m:'a1',
f:'a2'
}
}
let name1=toRef(person,'name')
console.log('####',name1)
// const x = toRefs(person)
// console.log('******',x)
function changeName(){
person.name = 'aa'
console.log('name1.value',name1.value) //'aa',但视图不更新,使用reactive响应式定义时视图会更新
console.log('person.name',person.name) //'aa',但视图不更新,使用reactive响应式定义时视图会更新
}
function changeName1(){
name1.value = 'bb'
console.log('name1.value',name1.value)//'bb',但视图不更新,
console.log('person.name',person.name)//'bb',但视图不更新,
}
return {person,name1,changeName,changeName1}
8.readonly和shallowReadonly
把响应式数据变为只读,shallowReadonly只保证第一层
let person = reactive({
name: "a",
p: {
m: "a1",
f: "a2",
},
});
let person1 = readonly(person);
let person2 = shallowReadonly(person);
function changeName() {
person1.name = "b";
person1.p.m = "b1";
console.log("readonly", person1);
person2.name = "b";
person2.p.m = "b1";
console.log("shallowReadonly", person2);
}
return { person, person1, changeName };
下图中person1改变是因为person发生变化导致person原对象发生了变化,可知,改变原person对象也会影响值
8、toRow,markRow
toRow将一个响应式对象转换为非响应式。
markRow标记一个普通对象后,该对象不能再创建响应式对象;对响应式对象不起作用;有些值不应被设置为响应式的,例如复杂的第三方类库等。当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
let oriPerson = {
name: "a",
p: {
m: "a1",
f: "a2",
},
}
oriPerson = markRaw(oriPerson)
let person = reactive(oriPerson);
//person的变化不会再引起页面的变化
9、shallowReactive和shallowRef
shallowReactive只处理第一层数据, 发现一个问题,如果同时更新了第一层属性和第二层属性,则深层属性也可以实现视图刷新,因为第一层属性更新的时候会触发视图的更新,单独更新则不行
shallowRef只能将基础类型转换为响应式,如shallowRef(0)
let oriPerson = {
name: "a",
p: {
m: 'a1',
f: "a2",
},
}
let person = shallowReactive(oriPerson);
function changeName() {
person.name = "b";
person.p.m = {nn:1};
console.log("markRaw", person);
}
10、customRef
自定义ref,可以实现防抖。之前一直搞不太懂防抖和节流区别今天看了一篇文章,防抖就是一个行为暂停一段时间才触发,节流是一个行为一直操作,一段时间触发一次时间,不等该行为停止。
function myRef(value, delay) {
let timer;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(val) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
value = val;
trigger();
},delay);
},
};
});
}
let a = myRef(10,3000);
function changeA(){
a.value = 20
}
11、teleport,fragment,suspense
teleport类似于穿梭框,可以将不属于该组件的元素穿梭到指定元素
//list组件
<teleport to="#aa">
<div>teleport</div>
</teleport>
//list父组件
<el-button @click="showList = !showList">切换组件</el-button>
<div id = 'aa'></div> //最终teleport内的元素会渲染到此处
<list v-if="showList"></list>
fragment虚拟dom元素,不会真的渲染,可以实现在template中不用唯一根元素的功能
suspense异步组件未加载之前显示的内容
<Suspense v-if="showList">
<template v-slot:default>
<list />
</template>
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
import { defineAsyncComponent } from "vue";
const list = defineAsyncComponent(
{
loader:() => import("./components/list.vue"),
delay:5000
}
);
12、其他改变
(1)全局API Vue=>app
(2)去除过滤器,移除keycode作为修饰符,去除$on,$off,$once等事件支持