Vue3.0的六大亮点
- Performance:性能比VUE2.x快1.2-2倍
- Tree shaking support按需编译,体积比Vue2.x更小
- Composition API:组合API(类似React Hooks)
- Better TypeScript support:更好的Ts支持
- Custom Renderer API:暴露了自定义渲染API
- Fragment,Teleport(Protal),Suspense:更先进的组件
性能更快
1、diff方法优化,Vue3 中仅对静态标记标记对象进行比较
在创建虚拟DOM的时候会根据DOM中的内容,会不会发生变化,添加静态标记。
- Vue2 中的虚拟dom是进行全量的对比
- Vue3 新增了静态标记(PatchFlag)
只比对带有 PF 的节点
并且通过 Flag 的信息得知当前节点要比对的具体内容
2、HoistStatic静态提升
将静态内容提取出来,变成常量再进行赋值
- Vue2中无论元素是否参与更新,每次都会重新创建
- Vue3中对于不参与更新的元素,只会被创建一次。以后每次进行render的时候,就不会重复创建这些静态的内容,而是直接从一开始就创建好的常量中取就行了
3、cacheHandlers事件侦听器缓存
onClick 方法被存入 cache。在使用的时候,如果能在缓存中找到这个方法,那么它将直接被使用。如果找不到,那么将这个方法注入缓存。总之,就是把方法给缓存了
4、ssr渲染
原教程教案
静态标记附录
组合API 官方文档链接
组合API的入口函数: setup
执行时间:先于beforeCreate
setup 中创建并 return 的所有东西,都将被得到外部的解析,无论是过去在 data 中创建的数据也好,还是在 methods 创建的方法也好,都将变成允许被响应式地使用,仿佛 Vue2 中的这些 API 都被融合在一起了一样,而实际上 Vue3 也是为了实现这个目的。
基础写法:
ref
本质:ref的本质其实还是reactive,当我们给ref函数传递一个值之后,ref函数底层会自动将ref转换成reactive。例:ref(18)->reactive({value: 18})。
什么是ref ?
- ref和reactive一样,也是用来实现响应式数据的方法
- 由于reactive必须传递一个对象,所以导致在企业开发中如果我们只想让某个变量实现响应式的时候会非常麻烦,所以Vue3提供了ref方法,实现简单值的监听。
Ref是这样的一种数据结构:它有个key为Symbol的属性做类型标识,有个属性value用来存储数据。这个数据可以是任意的类型,唯独不能是被嵌套了Ref类型的类型。
Ref类型的数据,是一种响应式的数据。
Ref写法简单,但也有弊端,它只能监听一些如数字、字符串、布尔之类的简单数据。复杂数据需要用到 reactive 。(实际上也可以用 Ref 来封装对象,只不过在访问上多一层 value ,稍微麻烦了一些)
ref注意点:
- 在Vue中使用ref的值不用通过value获取
- 在JS中使用ref的值必须通过.value获取
reactive
本质:就是将传入的数据包装成一个proxy对象
什么是reactive?
- reactive是vue3中提供的实现响应式数据的方法。
- 在vue2中响应式数据是通过defineProperty来实现的,在vue3中响应式数据是通过ES6的Proxy来实现的。具体参照Vue双向数据绑定,Vue3的Proxy和defineProperty的比较
reactive注意点:
- reactive参数必须是对象(json/arr)
- 如果给reactive传递了其他对象。默认情况下,修改对象无法实现界面的数据绑定更新。如果需要更新,需要进行重新赋值。
- 基本类型(数字、字符串、布尔值)在reactive中无法被创建成proxy对象,也就无法实现监听。
- reactive中使用Date参数需要特殊处理,特殊处理就是重新赋值。
ref和reactive的区别:
如果在template里使用的是ref类型的数据,那么Vue会自动添加.value
如果在template里使用的是reactive类型的数据,那么Vue不会自动添加.value(这里的.value指的是对象里面的键名)
Vue如何决定是否需要自动添加.value?
Vue在解析数据之前,会自动判断这个数据是否是ref类型的。
vue怎么区分是ref还是reactive定义的数据?
通过当前数据的__v_ref来判断的。
如果有这个私有的属性,并且取值为true,那么就代表是一个ref类型的数据
开发者怎么区分是ref还是reactive定义的数据?
import { isRef, isReactive } from 'vue';
let age = ref(18);
let list = reactive([1, 2, 3, 4]);
let obj = reactive({
person: {
name: 'lisa',
age: '10'
}
})
function myFn() {
console.log(isRef(age)); // true
console.log(isReactive(age)); // false
console.log(isRef(list)); // false
console.log(isReactive(list)); // true
console.log(isRef(obj)); // false
console.log(isReactive(obj)); // true
}
组合API ref、reactive简单示例代码:
<script>
import { ref, reactive } from 'vue';
export default {
name: 'App',
setup() {
let count = ref(0);
function myFn() {
// do somthing
// 注意点:如果是通过ref创建的数据,在template中使用,不用通过.value来获取,因为vue会自动给我们添加.value
// 在Vue中使用ref的值不用通过.value获取
// 在JS中使用ref的值必须通过.value获取
count.value = 18;
}
//let msg = reactive({
// num: {
// inner: 0
// }
//})
let msg = reactive([1,2,3])
let state = reactive({
time: new Date()
});
function changeTime() {
// 直接修改以前的,界面不会更新
// state.time.setDate(state.time.getDate() + 1);
// 特殊处理:重新赋值
const newTime = new Date(state.time.getTime());
newTime.setDate(state.time.getDate() + 1);
state.time = newTime;
}
//在组合API中定义的变量/方法,要想在外界使用,必须通过return { xxx, xxx }暴露出去
return { count, myFn, msg, changeTime,state };
}
}
</script>
组合API功能代码封装, 简单代码如下:
在上面的基础上,再加一个封装模块,多个封装模块的情况下,通过传参可以使用其他封装模块的数据:
多个封装模块的另一种写法,将封装的代码放到另外的js文件,通过import引入该js文件,举例如下:
customRef
返回一个ref对象,可以显式地控制依赖追踪和触发相应
<script>
import { ref, customRef } from 'vue';
function myRef(value) {
return customRef((track, trigger) => {
// fetch 是es6 es7中的异步请求方法
fetch(value).then((res) => {
return res.json();
})
.then((data) => {
console.log(data);
value = data;
trigger();
})
.catch((err) => {
console.log(err);
})
return {
get() {
track(); // 告诉Vue这个数据是需要追踪变化的
console.log('get', value);
// 注意点:不能再get方法中发送网络请求
return value;
},
set(newValue) {
console.log('set', newValue);
value = newValue;
trigger(); // 告诉Vue触发界面更新
}
}
})
}
export default {
name: 'App',
setup() {
// 同步
// let state = myRef(12);
// function myFn() {
// state.value += 1;
// }
// 异步
let state = myRef('路径')
return { state, myFn };
}
}
</script>
readonly、shallowReadonly、isReadonly
readonly:用于创建一个只读的数据,并且是递归只读
shallowReadonly:用于创建一个只读数据,但是不是递归只读
isReadonly:判断是否是readonly定义的变量
const和readonly的区别:
const:赋值保护,不能重新赋值,但是对象里面的属性值可变。例:
const obj = {name: 'ljh', attr: {age: 10, height: 1.2}};
obj = 'test'; // 报错, const定义的变量不可重新赋值
obj.name = 'test'; // 不报错,值可以变化
readonly:属性保护,不能给属性重新赋值
let state = readonly({name: 'ljh', attr: {age: 10, height: 1.2}})
state.name = 'test';
state.attr.age = 9;
state.attr.height = 1.1;
// state的全部属性都是只读,不会有变化
shallow(重要)
非递归监听:shallowRef、shallowReactive
没有shallow,对原始的值进行整个的 reactive 化,如果有shallow,那么只对最外层的数据执行监听。
通过shallowRef创建的数据,监听的是.value的变化
如何触发非递归监听属性更新界面?
shallowRef:
- 可以通过triggerRef触发;
- 通过给.value赋值触发;
shallowReactive: - 给最外层的数据赋值
简单示例代码:
<script>
import { shallowRef, triggerRef, shallowReactive } from 'vue'
export default {
name: 'App',
setup() {
let msg1 = shallowRef({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg1);
<!-- shallowRef start -->
// 如果是通过shallowRef创建数据,那么vue监听的是.value的变化,并不是第一层的变化
// 所以以下写法页面展示值不会更新
// msg1.value.a.b.c = 'C';
// msg1.value.e.f = 'F';
// msg1.value.g = 'G';
// 1.由于只有最外层的数据改变能被监测到,所以只有在直接改变 msg1.value 的时候才会产生监测,页面展示值会变
msg1.value = {
a: {
b: {
c: 'C'
}
},
e: {
f: 'F'
},
g: 'G'
}
<!-- shallowRef end -->
<!-- triggerRef start -->
// 2. 对于 shallow 过的 ref 对象,我们还可以手动去触发 ref 的变化监听事件来实现界面的改变,使用的api是triggerRef
msg1.value.a.b.c = 'C';
msg1.value.e.f = 'F';
msg1.value.g = 'G';
triggerRef(msg1);
<!-- triggerRef end -->
// 以上两种写法可以达到相同的效果
};
// shallowReactive 可以实现类似 shallowRef 的功能
let msg2 = shallowReactive({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function d() {
<!-- shallowReactive start -->
msg2.a.b.c = 'C';
msg2.e.f = 'F';
msg2.g = 'G';
console.log(msg2);
<!-- shallowReactive end -->
}
return { msg1, msg2, c, d };
}
}
</script>
手写shallowRef、shallowReactive
toRaw
一个用来优化资源加载的方案
想修改定义的内容而不引起界面的改动,就只需要用 toRaw 取出它的 “源值” 进行修改就行了。
<template>
<div>
<p>{{state}}</p>
<button @click="myFn"></button>
</div>
</template>
<script>
import { reactive, ref } from 'vue'
export default {
name: 'App',
setup() {
let obj = {name: 'ljh', age: 12};
/ **
ref、reactive数据类型的特点:
每次修改都会被追踪,都会更新UI界面,这样非常消耗性能
如果有一些操作不需要追踪,不需要更新UI界面,那么这个时候,就可以通过toRaw方法拿到它的原始数据,对原始数据进行修改,这样就不会被追踪,也不会更新UI界面,性能得到了优化
**/
let state = reactive(obj);
let obj2 = toRaw(state); // 拿到reactive原始数据
// console.log(obj === obj2); // true
// console.log(obj === state); // false
// state和obj的关系:
// 引用关系,state的本质是一个Proxy对象,在这个Proxy对象中引用了Obj
function myFn() {
// 如果直接修改obj,是无法触发界面更新的
// 修改通过toRaw拿到的原始数据,不会更新界面
// 只有通过包装之后的对象来修改,才会触发界面的更新
<!--obj.name = 'test'; // obj、state的值改变,但是界面不会更新-->
console.log(obj); // {name: "test", age: 12}
console.log(state); // {name: "test", age: 12}
<!--obj2.name = 'test'; // 界面不更新-->
<!--state.name = 'test'; // 界面更新-->
}
let state3 = ref(obj);
let obj3 = toRaw(state3.value); // 拿到ref原始数据
return { state, state3, myFn };
}
}
</script>
markRaw
永远无法被追踪,无法被改变
<script>
import { reactive, markRaw } from 'vue'
export default {
name: 'App',
setup() {
let obj = {name: 'ljh', age: 12};
obj = markRaw(obj);
let state = reactive(obj);
function myFn() {
state.name = 'test'; // 界面state永远不会更新
}
return { state, myFn };
}
}
</script>
toRef
ref和toRef的区别:
ref->复制,修改响应式数据不会影响以前的数据
toRef->引用,修改响应式数据会影响以前的数据
ref->数据发生变化,界面就会自动更新
toRef->数据发生变化,界面不会自动更新
应用场景:如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新UI,那么就可以使用toRef。
<script>
import { ref, toRef } from 'vue'
export default {
name: 'App',
setup() {
let obj = {name: 'ljh'};
<!--let state = ref(obj.name);-->
let state = toRef(obj, 'name');
function myFn() {
state.value = 'test';
}
<!--如果利用ref将某一个对象中的属性变成响应式数据,此时修改响应式数据是不会影响到原始数据的-->
<!--console.log(obj); // {name: 'ljh'}-->
<!--console.log(state); // Proxy对象,_value值是'test'-->
<!--如果利用toRef将某一个对象中的属性变成响应式数据,此时修改响应式数据会影响到原始数据,但是并不会触发界面更新-->
console.log(obj); // {name: 'test'}
console.log(state); // Proxy对象,_value值是'test'
return { state, myFn };
}
}
</script>
toRefs
为了将多个数据都设置监听,或者对整个对象的所有数据发起监听,vue3 提供了便利的 API toRefs
<script>
import { toRefs } from 'vue'
export default {
name: 'App',
setup() {
let obj = {name: 'ljh', age: 10};
let state = toRefs(obj);
function myFn() {
state.name.value = 'test';
state.age,value = 8;
console.log(obj); // {name: 'test', age: 8}
console.log(state); // Proxy对象
}
return { state, myFn };
}
}
</script>
Vue3.0递归监听、非递归监听、数据追踪、性能优化
默认情况下,Vue3 中的 ref 和 reactive 都是递归监听的,即能实时监听对象的底层变化。
只要我们对 ref 和 reactive 中的内容进行更改,在默认情况下,只要更改的对象不是我们在 Vue3-4 中提到的类似 Date 的类型,都是能察觉到并且进行双向数据绑定的。
在默认情况下,递归监听肯定是好的,它让数据的变化能被实时监测到。然而它也带来了性能消耗的问题。
- 递归监听存在的问题。如果数据量比较大,非常消耗性能。
- 非递归监听。shallowRef / shallowReactive。
- 如何触发非递归监听属性更新页面?如果是shallowRef类型数据,可以通过triggerRef来触发
- 应用场景。一般情况下使用ref和reactive即可,只有在需要监听的数据量比较大的时候,才会使用shallowRef、shallowReactive。
Vue3.0响应式数据本质
Vue2.x:通过defineProperty实现响应式数据
Vue3.x:通过Proxy实现响应式数据
Proxy注意点:
- set方法必须通过返回值告诉Proxy此次操作是否成功
let obj = { name: 'ljh', age: '20' }
let state = new Proxy(obj, handler: {
get(obj, key) {
// obj: { name: 'ljh', age: '20' }
// key: name
return obj[key]
},
set(obj, key, value) {
obj[key] = value
// 更新UI界面
return true
}
})
Vue3.0生命周期
- beforeCreate → 请使用 setup()
- created → 请使用 setup()
- beforeMount → onBeforeMount
- mounted → onMounted
- beforeUpdate → onBeforeUpdate
- updated → onUpdated
- beforeDestroy(vue3别名:beforeUnmount)→ onBeforeUnmount
- destroyed(vue3别名:unmounted)→ onUnmounted
- errorCaptured → onErrorCaptured
- renderTracked(vue3新增) → onRenderTracked
- renderTriggered(vue3新增)→ onRenderTriggered
获取页面元素
<template>
<div ref="box"></div>
</tempalte>
import { ref, onMounted } from 'vue'
export default {
name: 'getDom',
setup() {
let box = ref(null)
onMounted(() => {
console.log('onMounted', box.value)
})
console.log('setup', box.value)
return { box }
}
}
备注:
vue2转换代码网站:https://template-explorer.vuejs.org/
vue3转换代码网站:https://vue-next-template-explorer.netlify.app/
文章截图代码来自知播渔李南江老师