vue2x diff 中 在 虚拟dom 数据有变化了上次渲染dm树和数据更新后的dom树,中每一个虚拟dom元素都会进行对比。
在vue3.0 diff 中在创建虚拟dom的时候会根据dom的内容是否会改变,添加一个静态标记,然后只比较有静态标记的虚拟dom元素,这样减少了比较的次数,性能就会又大幅度提升。
点击在Vue 3 Template Explorer中查看详情
在vue3 Template Explorer 中测试:
<div>
<p>123p>
<p>123p>
<p>123p>
<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。
默认情况下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 和 react hook 中的 usestate 类似 保存响应状态使用的。
composition-api option-api, composition-api 本质就是把 数据,方法 注入option-api、中 data methods。 composition-api在形式上是在setup内部进行编程的。
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)
}
}
}
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都是递归监听 这种递归监听是非常消耗性能的 因为我们的这两种方法 都是把数据包装成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将某一个对象中的属性变成响应式的数据,我们修改响应式的数据是会影响到原始数据的,但是如果响应式的数据是通过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这两个 并不常用 主要用于优化。
联想到了什么 (有那味儿了–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
};
},
};
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>
顾名思义就是只读的状态 const state = readonly({name:'zs',age:18})
readonly 用于创建一个只读的数据 并且是递归监听 。 const和 readonly的却别 const 是复制保护不能给变量重新赋值但是可以更改属性的值,readonly 是属性保护,不能给属性重新赋值。
shallowReadonly 只读并是非递归监听。
isReadonly 判断一个状态是否为只读的 isReadonly(state)
。