vue3 效率的提升、composition-api 和 ref 详解
vue3中效率为什么会提升很多,composition-api 和ref reactive、setup 等详解,带你更快的学习,使用vue3.0。
vue3 效率的提升、composition-api 和 ref 详解
vue3 效率的提升
优化diff算法
-
vue2x diff 中 在 虚拟dom 数据有变化了上次渲染dm树和数据更新后的dom树,中每一个虚拟dom元素都会进行对比。
-
在vue3.0 diff 中在创建虚拟dom的时候会根据dom的内容是否会改变,添加一个静态标记,然后只比较有静态标记的虚拟dom元素,这样减少了比较的次数,性能就会又大幅度提升。
hoistStatic静态提升
- Vue2中 无论元素是否参与更新,每次都会重新创建,然后再渲染。
- Vue3中对于不参与更新的元素,会做静态提升,只会被创建次,在渲染时直接复用即可。
在vue3 Template Explorer 中测试:
<div>
<p>123</p>
<p>123</p>
<p>123</p>
<p>{{msg}}</p>
</div>
静态提升之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "123"),
_createVNode("p", null, "123"),
_createVNode("p", null, "123"),
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
静态提升之后:
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "123", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "123", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "123", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_hoisted_2,
_hoisted_3,
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST
由此可见在render的时候不会_createVNode 不会改变的静态p。
cacheHandlers事件侦听器缓存
-
默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
-
//开启事件缓存之前 export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _ctx.onClick }, null, 8 /* PROPS */, ["onClick"]) ])) } // 请注意这个8 静态标记
-
//开启事件缓存之后 export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args))) }) ])) } //静态标记没有了,不会去对比,追踪
ref 和 reactive,setup和composition-api
新增的 ref、reactive 和 react hook 中的 usestate 类似 保存响应状态使用的。
composition-api option-api, composition-api 本质就是把 数据,方法 注入option-api、中 data methods。 composition-api在形式上是在setup内部进行编程的。
setup和composition-api
composition-api的形式
//composition-api
export default {
setup(){
const state = ref(0)
function myFun() {
console.log(state)
}
return {
state,
myFun
}
}
}
option-api的形式
export default {
data:function() {
return {
state:0
}
},
methods:{
myFun() {
console.log(this.state)
}
}
}
setup
- setup的在执行的时候,还没有执行created生命周期的方法,在beforeCreate和created之间 因此在setup中我们并不能使用this,在vue3.0 setup中this被赋值为undefined。
ref 和 reactive
-
reactive 是vue3.0中提供的复杂数据响应式的方法,在vue2中数据响应式是通过defineProperty,在vue3中数据响应式是通过Proxy来实现的。reactive的本质是把传入的数据包装成一个proxy对象,因此如果值传入一个简单值的话会出现界面无法更新的情况。例如:
reactive(123)
。 -
reactive 传入的值可以是 对象,数组(会转化为Proxy对象),但是如果是 new出来的对象则需要重新赋值的形式 例如 new Date()。
-
ref 的本质其实也是reactive ,ref 接受到一个值后ref 函数的底层会自动将ref转化为reactive,ref(1) -> reactive({ value : 1 }) 所以修改ref 值得时候需要 例如:
const state = ref(0); state.value = 1;
但是尤其要注意 我们在temlpate中使用ref创建的响应式数据库的时候,我们并不需要在 其中这样{{state.value}}
,这样就可以了{{state}}
在我们的template中 或自动生成这个value。 -
reactive 对参数是引用对象,他也仅仅是因为了当前的对象地址,如果修改原对象,state也在内存中会改变但是在界面上是不会改变的例如:
-
setup(){ let obj = {name:'ls', age:20} //但是如果是Ref 就不同了 Ref是对原数据的一份复制 const state = reactive(obj) function myFun() { obj.name = 'zs' //state中的值在内存中会改变,但是不会更新ui //只有通过包装过后的proxy对象改变,才会更新ui } return { state, myFun } }
-
通过上面例子我们可以扩展一下,更新ui其实是消耗性能的,但是如果我们有一些操作并不需要更新ui,但是我们的state又改变了 我们又应该怎么做才能不更新ui呢? 通过使用toRaw 可以你拿到 ref或则 reactive的值的原始数据如:
-
setup(){ let obj = {name:'ls', age:20} const state = reactive(obj) let obj2 = toRaw(state) function myFun() { obj2.name = 'zs' console.log(obj2) // {name:'zs', age: 20} console.log(state)// Proxy {name:'zs', age: 20} } return { state, myFun } }
-
这样我们更新obj2 只会在内存中更新 ui 却不更新。toRaw 作用就是 通过state(proxy对象)拿到原始的数据。
-
由此 我们又能引入 一个东西 ,如果我们希望我们的数据永远不会被追踪,我们可以使用markRaw,来防止更新ui。
-
setup(){ let obj = {name:'ls', age:20} obj = markRaw(obj) const state = reactive(obj) function myFun() { state.name = 'zs' console.log(state)// Proxy {name:'zs', age: 20} } return { state, myFun } }
ref和reactive的区别
- 在reactive中,并不会在template中自动加上.value 属性 。
- 在ref中,在template中使用会响应数据会默认加上.value的属性。 在ref上会有一个私有属性( __v_isRef ) vue3是通过 这个私有属性来判断是否添加.value属性的,vue3.0提供了两个api帮助我们自主判断这个类型分别为 isRef() 和isReactive() 。
- ref 的值 是对原数据的一份复制 reactive 的值 是对原数据的引用。
递归监听和非递归监听(shallowRef和shallowReactive)
-
默认情况下ref和reactive都是递归监听 这种递归监听是非常消耗性能的 因为我们的这两种方法 都是把数据包装成proxy对象的 递归监听的话就会使每一次 对象都包装成一个proxy对象 ,例如如下数据类型:
-
const state = reactive({ test:'lalala', a :{ test1:'123', b:{ test2:'456', c:{ test3:'456', } } } })
-
非递归监听 只能监听第一层的数据 使用方法如
import {shallowRef, shallowReactive} from 'vue'
引入方式变成了这两个代替 ref 和 reactive 用法还是一样的。shallowReactive 由于只包装了第一层,如果我们不更新第一层数据,只更新第二层或则其他层则不会更新ui。 shallowRef Vue监听的是.value的变化并不是第一层的变化。 -
由此之外Vue3中还提供了一个triggerRef(state)的方法 这个方法可以主动更新state ,用法上可以理解成react hook中的setState 但是Vue3中并没有提供triggerReactive的方法。
ref 和 toRef、toRefs
-
如果利用ref将某一个对象中的属性变成响应式的数据,我们修改响应式的数据是不会影响到原始数据的。
-
如果利用toRef将某一个对象中的属性变成响应式的数据,我们修改响应式的数据是会影响到原始数据的,但是如果响应式的数据是通过toRef创建的,那么修改了数据并不会触发UI界面的更新, toRef 有点类似reactive 都是对对象的引用 但是这个toRef 是对对象中的属性 单一属性。
-
setup(){ let obj = {name: 'ls'} let state = toRef(obj,'name') function myFun() { state.value = 'zs' console.log(obj) // {name:'zs'} console.log(state) //ObjectRefImpl {_object: {…}, _key: "name", __v_isRef: true} } return { state, myFun } }
-
如果想传入多个属性则需要使用toRefs 用法
let state = toRefs(obj)
toRef和toRefs这两个 并不常用 主要用于优化。
自定义ref (customRef)
联想到了什么 (有那味儿了😏–hook)自定义Ref 自定义hook?
<script>
import { ref,reactive,customRef} from "vue";
function myRef(value) {
return customRef((track, trigger) => {
return {
get() {
track() //追踪数据的变化
console.log(value)
return value
},
set(newValue) {
console.log(newValue)
value = newValue
trigger() //告诉Vue触发界面更新
}
}
})
}
export default {
name: "App",
setup() {
const age = myRef(18)
function myFun() {
age.value ++
console.log(state)
}
return {
age
};
},
};
</script>
应用场景 我们可以使用自定义Ref 来发请求 简化代码,在setup中我们是没有办法直接使用async await 的,但是我们可以使用 customRef 做出类似效果。
import { ref,customRef} from "vue";
function myRef(value) {
return customRef((track, trigger) => {
fetch(value)
.then(data => {
return data.json()
})
.then(data => {
value = data
trigger()
})
.catch(err => {
console.log(err)
})
return {
get() {
track() //追踪数据的变化
console.log(value)
return value
},
set(newValue) {
console.log(newValue)
value = newValue
trigger() //告诉Vue触发界面更新
}
}
})
}
export default {
name: "App",
setup() {
const data = myRef('../public/data.json') // 拿到json数据
return {
data
};
},
};
通过 ref 获取元素节点
vue3中ref还有一个用途就是获取元素的节点(需要配合生命周期函数使用,生命周期函数需要单独引入),如下:
<template>
<div ref="box"> lalala </div>
</template>
<script>
import { ref,onMounted} from "vue";
export default {
name: "App",
setup() {
let box = ref(null)
onMounted(() => {
console.log(box.value) // 获取到的 dom元素
})
return {
box
};
},
};
</script>
readonly shallowReadonly isReadonly
顾名思义就是只读的状态 const state = readonly({name:'zs',age:18})
readonly 用于创建一个只读的数据 并且是递归监听 。 const和 readonly的却别 const 是复制保护不能给变量重新赋值但是可以更改属性的值,readonly 是属性保护,不能给属性重新赋值。
shallowReadonly 只读并是非递归监听。
isReadonly 判断一个状态是否为只读的 isReadonly(state)
。
更多推荐
所有评论(0)