Appearance
Vue3.0 六大亮点:
Performance:性能比Vue2.x快 2.2 倍。Tree shaking support:按需编译,体积比Vue2.x更小。Composition API:组合API(类似React Hooks)。Better TypeScript support:更好的TS支持。Custom Renderer API:暴露了自定义渲染API。Fragment,Teleport(Protal),Suspense:更先进的组件。
Vue3.0 是如何变快的。
diff 方法优化:
Vue2.x中虚拟dom是进行全量的对比。Vue3.0新增了静态标记(PatchFlag)。在与上次虚拟节点进行对比时候,只对比带有
patch flag的节点。并且可以通过
flag的信息得知当前节点要对比的具体内容。
hoistStatic 静态提升:
Vue2.x中无论元素是否参与更新,每次都会重新创建。Vue3.0中对于不参与更新的元素,只会被创建一次,之后会在每次渲染的时候被不停的复用。
cacheHandlers 事件侦听器缓存
- 默认情况下
onClick会被视为动态绑定,所以每次都会去追踪它的变化。但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
SSR 渲染
当有大量静态内容的时候,这些内容会被当做纯字符串推进一个
buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样比虚拟dom来渲染的快上很多。当静态内容达到一定量级时候,会用
_createStaticVNode方法在客户端生成一个static node,这些静态node,会被直接innerHTML,就不需要创建对象,然后根据对象渲染。
组合 API
setup函数是组合API入口函数。
import { ref } from 'vue'
setup() {
let count = ref(0)
function fn() {
console.log(count)
}
return { count, fn };
}import { ref } from 'vue'
setup() {
let count = ref(0)
function fn() {
console.log(count)
}
return { count, fn };
}组合 API 中定义变量或者函数必须 return 暴露给外界;定义方法不必定义到 methods中, 直接在组合 API 中定义即可。
Ref 只能监听简单类型的变化,不建议监听复杂类型(数组/对象等)的变化。
Reactive 可以监听复杂类型的变化。
setup() {
let list = reactive({
lists: [{id: 1, name: 'zs'}]
})
return {list}
}setup() {
let list = reactive({
lists: [{id: 1, name: 'zs'}]
})
return {list}
}组合 API 本质:
在 setup 中注册的变量或函数,会自动注册到 vue2.x 中的 data 或 methods 中。
注意点:
执行
setup函数,是在beforeCreate钩子之前完成的。在
setup函数中,无法使用data和methods。在
setup函数中,this指向undefined。setup函数只能是同步的不能是异步的。
Reactive
什么是 Reactive。
Reactive是Vue3.0中提供的实现响应式数据的方法。在
Vue2.0中响应式数据是通过defineProperty实现,在Vue3.0中响应式数据通过ES6的Propxy实现。
Reactive 注意点:
Reactive函数是对象(json或arr)。在创建响应式数据时,传递的不是对象,则无法更新视图。
如果给
Reactive传递其他对象:默认情况下修改对象,界面不会自动更新。
如果想更新,可以通过重新赋值的方式。
setup() {
let state = reactive({
time: new Date()
})
function fn() {
const newTime = new Date(state.time.getTime())
newTime.setDate(state.time.getDate() + 1)
state.time = newTime
}
return { state, fn}
}setup() {
let state = reactive({
time: new Date()
})
function fn() {
const newTime = new Date(state.time.getTime())
newTime.setDate(state.time.getDate() + 1)
state.time = newTime
}
return { state, fn}
}Ref
什么是 Ref。
Ref是用来实现响应式数据的方法。由于
Reactive传入一个对象,导致在开发中如果只想让某个变量实现响应式时会比较麻烦,因此Vue3.0提供了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。
如何决定是否需要自动添加 .vlue
Vue解析数据之前,会自动判断这个数据是否是Ref类型的;如果是,就自动添加.value,如果不是就不自动添加.value。
如何判断当前的数据是否是 Ref 类型
- 通过当前数据的
__v_ref来判断的。如果有这个私有属性,并且取值为true,就代表是一个Ref类型的数据。
如何自行判断是 Ref,还是 Reactive
- 通过
isRef和isReactive。
import {ref, isRef, isReactive} from 'vue'
setup() {
let count = ref(18)
console.log(isRef(count))
console.log(isReactive(count))
return {count}
}import {ref, isRef, isReactive} from 'vue'
setup() {
let count = ref(18)
console.log(isRef(count))
console.log(isReactive(count))
return {count}
}递归监听
默认情况下,无论是通过 Ref 还是 Reactive 都是递归监听。
递归监听存在的问题:
- 如果数据量较大时,非常消耗性能。
创建递归监听,如:
<template>
<p>{{state.a}}</p>
</template>
import {reactive} from 'vue'
export default {
setup() {
let state = reactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
})
state.a = 1;
state.gf.b = 2
console.log(state.a, state, state.gf.b, state.gf)
return {state}
}
}<template>
<p>{{state.a}}</p>
</template>
import {reactive} from 'vue'
export default {
setup() {
let state = reactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
})
state.a = 1;
state.gf.b = 2
console.log(state.a, state, state.gf.b, state.gf)
return {state}
}
}监听到每一层数据变化, 界面 UI 更新数据。
非递归监听
非递归监听:只能监听第一层, 不能监听其他层;即只有第一层包装成 proxy。
创建非递归监听,使用 shallowReactive 和 shallowRef。
注意点:
如果是通过
shallowReactive创建数据,只要第一层数据变化,就会更新界面UI第二层,第三层等等数据;如果第一层数据不更新,界面UI不会更新。如果是通过
shallowRef创建数据,Vue监听的是.value的变化,并不是第一层数据的变化。如果是通过
shallowRef创建数据,想监听第n层数据,并主动更新UI界面,这时使用triggerRef方法。Vue3.0只提供了triggerRef方法,没有提供triggerReactive方法;因此如果使用triggerReactive类型的数据,无法主动触发更新界面。
<template>
<p>{{ state.a }}</p>
<p>{{ state.gf.b }}</p>
</template>
import {reactive} from 'vue'
export default {
setup() {
let state = shallowReactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
})
/*
let state = shallowRef({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
})
// state.a.value = 1;
// state.gf.b.value = 2
state.gf.f.s.d.value = 4;
triggerRef(state)
*/
state.a = 1;
state.gf.b = 2
console.log(state.a, state, state.gf.b, state.gf)
return {state}
}
}<template>
<p>{{ state.a }}</p>
<p>{{ state.gf.b }}</p>
</template>
import {reactive} from 'vue'
export default {
setup() {
let state = shallowReactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
})
/*
let state = shallowRef({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
})
// state.a.value = 1;
// state.gf.b.value = 2
state.gf.f.s.d.value = 4;
triggerRef(state)
*/
state.a = 1;
state.gf.b = 2
console.log(state.a, state, state.gf.b, state.gf)
return {state}
}
}shallowRef 的本质
shallowRef 底层调用 shallowReactive,即: shallowRef(18) --> shallowReactive({value: 18})。
如果是通过 shallowRef 创建的数据,它监听的是 .value 的变化。因为底层本质上 .value 才是第一层。
import {reactive} from 'vue'
export default {
setup() {
let state = shallowReactive({
value: {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
}
})
/*
let state = shallowRef({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
})
state.a.value = 1;
state.gf.b.value = 2
*/
state.a = 1;
state.gf.b = 2
console.log(state.a, state, state.gf.b, state.gf)
return {state}
}
}import {reactive} from 'vue'
export default {
setup() {
let state = shallowReactive({
value: {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
}
})
/*
let state = shallowRef({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd'
}
}
}
})
state.a.value = 1;
state.gf.b.value = 2
*/
state.a = 1;
state.gf.b = 2
console.log(state.a, state, state.gf.b, state.gf)
return {state}
}
}toRaw
例子:
<template>
<p>{{obj.name}}</p>
</template>
export default {
setup() {
let obj = { name: 'zs', age: 18 }
obj.name = 'ming'
return {obj}
}
}<template>
<p>{{obj.name}}</p>
</template>
export default {
setup() {
let obj = { name: 'zs', age: 18 }
obj.name = 'ming'
return {obj}
}
}修改名字数据发生改变,UI 没有更新,即不是响应式数据。如果先变成响应式使用 Ref 或者 Reactive。
<template>
<p>{{state.name}}</p>
</template>
import {reactive} from 'vue'
export default {
setup() {
let obj = { name: 'zs', age: 18 }
//obj.name = 'ming'
let state = reactive(obj);
state.name = 'ming'
console.log(obj == state) // false
return {state}
}
}<template>
<p>{{state.name}}</p>
</template>
import {reactive} from 'vue'
export default {
setup() {
let obj = { name: 'zs', age: 18 }
//obj.name = 'ming'
let state = reactive(obj);
state.name = 'ming'
console.log(obj == state) // false
return {state}
}
}从上述得知,state 与 obj 是引用关系,state 引用了 obj。
如果直接修改 obj,是无法触发界面更新。
只有通过包装之后的对象修改,才会触发界面更新。
从 Reactive 中获取原始数据
<template>
<p>{{state.name}}</p>
</template>
import {reactive, toRaw} from 'vue'
export default {
setup() {
let obj = { name: 'zs', age: 18 }
let state = reactive(obj);
state.name = 'ming'
let obj2 = toRaw(state)
console.log(obj2, obj == obj2)
return {state}
}
}<template>
<p>{{state.name}}</p>
</template>
import {reactive, toRaw} from 'vue'
export default {
setup() {
let obj = { name: 'zs', age: 18 }
let state = reactive(obj);
state.name = 'ming'
let obj2 = toRaw(state)
console.log(obj2, obj == obj2)
return {state}
}
}Ref 或 Reactive 数据类型,每次修改都会被追踪,UI 界面都会被更新,这样非常消耗性能。如果有一些不需要追踪,不需要更新 UI 界面,这时就可以通过 toRaw 方法拿到原始数据,对原始数据进行修改,就不会被追踪,不会更新 UI 界面,性能提高。
从 Ref 中获取原始数据
<template>
<p>{{state.name}}</p>
</template>
import {ref, toRaw} from 'vue'
export default {
setup() {
let obj = { name: 'zs', age: 18 }
let state = ref(obj);
state.name = 'ming'
let obj2 = toRaw(state.value)
console.log(obj2, obj == obj2)
return {state}
}
}<template>
<p>{{state.name}}</p>
</template>
import {ref, toRaw} from 'vue'
export default {
setup() {
let obj = { name: 'zs', age: 18 }
let state = ref(obj);
state.name = 'ming'
let obj2 = toRaw(state.value)
console.log(obj2, obj == obj2)
return {state}
}
}从 Ref 中获取原始数据必须添加 .value,因为 Ref 底层是使用 Reactive。
markRaw
如果原始值永远不想被追踪变化,使用 markRaw。
例子:
<template>
<p>{{state}}</p>
</teamplate>
import {reactive, markRaw} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
obj = markRaw(obj)
let state = reactive(obj)
state.name = 'cherry'
return {state}
}
}<template>
<p>{{state}}</p>
</teamplate>
import {reactive, markRaw} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
obj = markRaw(obj)
let state = reactive(obj)
state.name = 'cherry'
return {state}
}
}toRef
和 Ref 一样,都是创建响应式数据。
使用 Ref 打印输出值:
<template>
<p>{{state}}</p>
</teamplate>
import {reactive, ref} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
let state = ref(obj.name)
state.value = 'cherry'
console.log(obj, state)
return {state}
}
}<template>
<p>{{state}}</p>
</teamplate>
import {reactive, ref} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
let state = ref(obj.name)
state.value = 'cherry'
console.log(obj, state)
return {state}
}
}结论:
如果使用 Ref 将某一个对象中的属性变成响应式的数据,修改响应式数据是不会影响到原始数据。
如果响应式数据通过 Ref 创建,修改了数据并会触发 UI 界面更新。
相当于 Ref 是复制一份原始值。
使用 roRef 打印输出值:
<template>
<p>{{state}}</p>
</teamplate>
import {reactive, toRef} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
let state = toRef(obj, 'name')
console.log(state)
state.value = 'cherry'
console.log(obj, state)
return {state}
}
}<template>
<p>{{state}}</p>
</teamplate>
import {reactive, toRef} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
let state = toRef(obj, 'name')
console.log(state)
state.value = 'cherry'
console.log(obj, state)
return {state}
}
}结论:
如果使用 toRef 将某一个对象中的属性变成响应式的数据,修改响应式数据会影响到原始数据。
如果响应式数据通过 toRef 创建,修改了数据并不会触发 UI 界面更新。
toRef 的本质是引用原始值。
应用场景:
- 如果让响应式数据和以前的数据关联起来,并且更新响应式数据之后并不想更新 UI 界面,就可以使用 toRef。
roRefs
如果数据是多个字段,使用 toRef 就必须写多个字段重新赋值:
<template>
<p>{{state}}</p>
</teamplate>
import {reactive, toRef} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
let state = toRef(obj, 'name')
let age = toRef(obj, 'age')
state.value = 'cherry'
age.value = 20
console.log(obj, state)
return {state}
}
}<template>
<p>{{state}}</p>
</teamplate>
import {reactive, toRef} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
let state = toRef(obj, 'name')
let age = toRef(obj, 'age')
state.value = 'cherry'
age.value = 20
console.log(obj, state)
return {state}
}
}上述可以通过 toRefs 简化,底层逻辑还是通过 toRef 实现。
<template>
<p>{{state}}</p>
</teamplate>
import {reactive, toRefs} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
let state = toRefs(obj)
console.log(obj, state)
state.name.value = 'cherry'
state.age.value = 20
return {state}
}
}<template>
<p>{{state}}</p>
</teamplate>
import {reactive, toRefs} from 'vue'
export default{
setup() {
let obj = {name: 'zs', age: 18}
let state = toRefs(obj)
console.log(obj, state)
state.name.value = 'cherry'
state.age.value = 20
return {state}
}
}customRef
理解为自定义 Ref。
返回一个 Ref 对象,可以显式的控制以来追踪和触发相应。
<template>
<p>{{state}}</p>
</teamplate>
import {ref, customRef} from 'vue'
export default{
setup() {
let state = myRef(18)
state.value = 20;
return {state}
}
}
function myRef(value) {
return customRef((track, trigger) => {
//track -> 追踪
//trigger -> 触发
return {
get(){
track() //告诉Vue 这个数据是需要追踪变化
return value
},
set(newVal) {
value = newVal
trigger() // 告诉Vue触发界面更新
}
}
})
}<template>
<p>{{state}}</p>
</teamplate>
import {ref, customRef} from 'vue'
export default{
setup() {
let state = myRef(18)
state.value = 20;
return {state}
}
}
function myRef(value) {
return customRef((track, trigger) => {
//track -> 追踪
//trigger -> 触发
return {
get(){
track() //告诉Vue 这个数据是需要追踪变化
return value
},
set(newVal) {
value = newVal
trigger() // 告诉Vue触发界面更新
}
}
})
}具体查看 Vue3.0 官方文档 API。
Ref 获取元素
在 Vue2.x 中,可以通过给元素添加 ref='xxx',然后在代码中通过 refs.xxx 方式获取元素。
在 Vue3.0 中,也可以通过 ref 获取元素,使用写法不同:
<template>
<div ref="box">box</div>
</template>
import {ref, onMounted} from 'vue'
export default {
setup() {
let box = ref(null)
onMounted(() => {
console.log(box.value) // ②
})
console.log(box.value) // ①
return {box}
}
}<template>
<div ref="box">box</div>
</template>
import {ref, onMounted} from 'vue'
export default {
setup() {
let box = ref(null)
onMounted(() => {
console.log(box.value) // ②
})
console.log(box.value) // ①
return {box}
}
}readonly 家族
- readonly:用于创建一个只读的数据,并且是递归只读。
<template>
<div>{{ state }}</div>
</template>
import {readonly} from 'vue'
export default {
setup() {
const value = {name: 'ls', age: 26}
let state = readonly({
name: 'zs',
attr{
age: 18,
height: 1.88
}
})
state.name = 'cherry'
state.attr.age = 20
state.attr.height = 1.77
value.name = 'xing'
value.age = 66
console.log(value)
return {state}
}
}<template>
<div>{{ state }}</div>
</template>
import {readonly} from 'vue'
export default {
setup() {
const value = {name: 'ls', age: 26}
let state = readonly({
name: 'zs',
attr{
age: 18,
height: 1.88
}
})
state.name = 'cherry'
state.attr.age = 20
state.attr.height = 1.77
value.name = 'xing'
value.age = 66
console.log(value)
return {state}
}
}结论:
- 修改响应式数据,修改后的数据没变化,UI 界面数据没变化。
const 和 readonly 区别:
const:赋值保护,不能给变量重新赋值
readonly:属性保护,不能给属性重新赋值
shallowReadonly:用于创建一个第一层只读的数据。
<template>
<div>{{ state }}</div>
</template>
import {shallowReadonly} from 'vue'
export default {
setup() {
let state = shallowReadonly({
name: 'zs',
attr{
age: 18,
height: 1.88
}
})
state.name = 'cherry'
state.attr.age = 20
state.attr.height = 1.77
return {state}
}
}<template>
<div>{{ state }}</div>
</template>
import {shallowReadonly} from 'vue'
export default {
setup() {
let state = shallowReadonly({
name: 'zs',
attr{
age: 18,
height: 1.88
}
})
state.name = 'cherry'
state.attr.age = 20
state.attr.height = 1.77
return {state}
}
}结论:
修改响应式数据,修改后的数据第一层没发生变化,第二层数据发生变化,并且 UI 界面没更新。
isReadonly:用于判断是否是 Readonly。
<template>
<div>{{ state }}</div>
</template>
import {isReadonly,shallowReadonly} from 'vue'
export default {
setup() {
let state = shallowReadonly({
name: 'zs',
attr{
age: 18,
height: 1.88
}
})
state.name = 'cherry'
state.attr.age = 20
state.attr.height = 1.77
console.log(isReadonly(state))
return {state}
}
}<template>
<div>{{ state }}</div>
</template>
import {isReadonly,shallowReadonly} from 'vue'
export default {
setup() {
let state = shallowReadonly({
name: 'zs',
attr{
age: 18,
height: 1.88
}
})
state.name = 'cherry'
state.attr.age = 20
state.attr.height = 1.77
console.log(isReadonly(state))
return {state}
}
}Vue3 响应式数据本质
Vue2.x 是通过 defineProperty 实现响应式数据。
Vue3.0 是通过 Proxy 实现响应式数据。
模拟:
let obj = { name: 'zs', age: 18 }
let state = new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, newVal) {
obj[key] = newVal
return true
}
})
state.name = 'cherry'
console.log(state)let obj = { name: 'zs', age: 18 }
let state = new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, newVal) {
obj[key] = newVal
return true
}
})
state.name = 'cherry'
console.log(state)实现 shallowReactive
function shallowReactive(obj) {
return new Proxy(obj, {
get() {
return obj[key]
},
set(obj, key, newVal) {
obj[key] = newVal
return true
}
})
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c'
}
}
}
let state = shallowReactive(obj)
state.a = 1
state.gf.b = 2
state.gf.f.c = 3
console.log(state)function shallowReactive(obj) {
return new Proxy(obj, {
get() {
return obj[key]
},
set(obj, key, newVal) {
obj[key] = newVal
return true
}
})
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c'
}
}
}
let state = shallowReactive(obj)
state.a = 1
state.gf.b = 2
state.gf.f.c = 3
console.log(state)实现 shallowRef
function shallowRef(val) {
return shallowReactive(obj: {value: val})
}
let state = shallowRef(obj)
state.value.a = 1
state.value.gf.b = 2
state.value.gf.f.c = 3
console.log(state)function shallowRef(val) {
return shallowReactive(obj: {value: val})
}
let state = shallowRef(obj)
state.value.a = 1
state.value.gf.b = 2
state.value.gf.f.c = 3
console.log(state)实现 Reactive
function Reactive(obj) {
if(typeof obj === 'object') {
if(obj instanceof Array) {
// 如果是一个数组,那取出数组中的每一个元素
// 判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装Proxy
obj.forEach((item, index) => {
if(typeof item === 'object') obj[index] = Reactive(item)
})
}else{
// 如果是一个对象,那么取出对象属性的取值
// 判断对象属性的取值是否又是一个对象,如果是一个对象,那么也需要包装成 Proxy
for(let key in obj) {
let item = obj[key]
if(typeof item === 'object') obj[key] = Reactive(item)
}
}
return new Proxy(obj, {
get() {
return obj[key]
},
set(obj, key, newVal) {
obj[key] = newVal
return true
}
})
}else{
console.log(`${obj} is not object`)
}
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c'
}
}
}
let state = Reactive(obj)
state.a = 1
state.gf.b = 2
state.gf.f.c = 3
console.log(state)function Reactive(obj) {
if(typeof obj === 'object') {
if(obj instanceof Array) {
// 如果是一个数组,那取出数组中的每一个元素
// 判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装Proxy
obj.forEach((item, index) => {
if(typeof item === 'object') obj[index] = Reactive(item)
})
}else{
// 如果是一个对象,那么取出对象属性的取值
// 判断对象属性的取值是否又是一个对象,如果是一个对象,那么也需要包装成 Proxy
for(let key in obj) {
let item = obj[key]
if(typeof item === 'object') obj[key] = Reactive(item)
}
}
return new Proxy(obj, {
get() {
return obj[key]
},
set(obj, key, newVal) {
obj[key] = newVal
return true
}
})
}else{
console.log(`${obj} is not object`)
}
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c'
}
}
}
let state = Reactive(obj)
state.a = 1
state.gf.b = 2
state.gf.f.c = 3
console.log(state)实现 Ref
function Ref(val) {
return Reactive(obj: {value: val})
}function Ref(val) {
return Reactive(obj: {value: val})
}