vue3 效率的提升、composition-api 和 ref 详解

vue3 效率的提升

优化diff算法

  • vue2x diff 中 在 虚拟dom 数据有变化了上次渲染dm树和数据更新后的dom树,中每一个虚拟dom元素都会进行对比。

  • 在vue3.0 diff 中在创建虚拟dom的时候会根据dom的内容是否会改变,添加一个静态标记,然后只比较有静态标记的虚拟dom元素,这样减少了比较的次数,性能就会又大幅度提升。

  • 点击在Vue 3 Template Explorer中查看详情

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)

Logo

前往低代码交流专区

更多推荐