浅谈Vue3.0ref、reactive与递归监听

2,202 阅读4分钟

本文会谈谈下面几个小玩意

  1. reactive
  2. ref
  3. 递归监听与非递归监听

ref、reactive

reactive

  1. reactive是Vue3中提供实现响应式数据的方法
    • Vue2中响应式数据是通过defineProperty实现的
    • Vue3中是通过ES6的Proxy实现的
  2. reactive注意点
    • reactive的参数必须是对象(json/arr)
    • 如果给reactive传递了其他对象
      • 修改对象,不会驱动视图更新
      • 如果想更新视图,可以重新赋值

ref

  1. 什么是ref?
    • ref和reactive一样,也是用来实现响应式数据的方法
    • 由于reactive必须传递一个对象,导致在日常的开发中如果我们只想让某个简单的变量实现响应式会变的很麻烦
      所以Vue3中提供了ref方法实现简单的监听
  2. ref本质
    • ref底层的本质其实就是reactive
    • 系统会根据ref传入的值转换成reactive的形式
    • 栗子🌰
      •   ref(123) => reactive({value:123})
        
let age = ref(18)
console.log(age)
// 下面是打印的log
      RefImpl {_rawValue: 18, _shallow: false, __v_isRef: true, _value: 18}
      __v_isRef: true
      _rawValue: 18
      _shallow: false
      _value: 18
      value: 18
      __proto__: Object
  1. ref注意事项:
    • 在Vue的template中使用ref不用通过value取值,系统会自动帮你添加
    • js/ts中使用用ref的值需要通过.value来获取
    • js中
      let age = ref(18)
      function handleChangeVal() {
      age.value = 2
      }
      
    • template中
      <p>{{a}}</p>
      
  2. ref和reactive区别
    • 如果template中使用ref类型数据,Vue会自动添加 .value
    • 如果template中使用reactive类型数据,Vue不会自动添加 .value
    • Vue会判断这个数据的类型,如果是ref就自动添加,否则反之
    • 打印ref类型数据可以看到有__v_isRef: true属性
    • 打印reactive类型数据可以发现压根就没有这个属性
      Proxy {value: 18}[[Handler]]: Object
      deleteProperty: ƒ deleteProperty(target, key)
      get: ƒ (target, key, receiver)
      has: ƒ has(target, key)
      ownKeys: ƒ ownKeys(target)
      set: ƒ (target, key, value, receiver)
      __proto__: Object
      [[Target]]: Object
      value: 18
      __proto__: Object
      [[IsRevoked]]: false
      
    • 通过Vue提供的isRef、isReactive方法也可以判断是那种类型

递归监听

默认情况下,无论是ref还是reactive,都是递归监听。
递归?你说的就是那个递归?没错,当数据量不大的时候递归一下还是ok的,但当数据量很大,递归监听就比较消耗性能了,你需要了解非递归监听

非递归监听

  1. shallowReactive
  2. shallowRef

shallowReactive

shallowReactive只会监听第一层的数据,只有当第一层数据发生改变,就会驱动视图更新。如果第一层数据没有发生改变,而是第二层,第三层数据发生改变,是不会驱动视图更新的。
现在有这么个数据

    let f = shallowReactive({
      gname:'grandpaName',
      grandpa:{
        fname:'fatherName',
        father:{
          sname:'sonName',
          s:{
            son:'son'
          }
        }
    

我们来打印下每个节点


console.log(f);						// => Proxy {gname: "grandpaName", grandpa: {…}}
console.log(f.grandpa);				// =>       {fname: "fatherName", father: {…}}
console.log(f.grandpa.father);		// =>       {sname: "sonName", s: {…}}
console.log(f.grandpa.father.s);	// => 		{son: "son"}
f.gname = '1';				// => 修改数据可以引起视图更新
f.grandpa.fname = '2'		// => 如果只修改这里不修改上层不会引起视图更新

可以看到只有第一层的被监听了,但是如果不修改第一层的数据,而修改第二层第三层甚至更下层的数据是不会引起视图更新的,不懂为啥修改第一层+第二层好使的自行百度vue diff算法,这里不做过多赘述。

shallowRef

还是那段熟悉的数据结构,这次我们换shallowRef来看看打印的结果

console.log(f.value);					// => {gname: "grandpaName", grandpa: {…}}
console.log(f.value.grandpa);			// => {fname: "fatherName", father: {…}}
console.log(f.value.grandpa.father);	// => {sname: "sonName", s: {…}}
console.log(f.value.grandpa.father.s);	// => {son: "son"}

通过shallowRef创建的数据是监听的.value的变化,并不是监听的第一层的变化。我们可以通过对value的修改来驱动视图更新。
如果我的业务就是想让我修改最后一个呢?那你这么搞是不是太麻烦了?
别着急,Vue给我们提供了triggerRef()方法,将我们需要改变的数据传进去,Vue就会帮我们来更新视图

f.value.grandpa.father.s.son = '4';
triggerRef(f)

⚠️ Vue只提供了triggerRef()方法,没有triggerReactive()方法!
shallowRef的底层是shallowReactive的实现

shallowRef(123) => shallowReactive({value:123})

与ref和reactive的关系一样,shallowRef底层实现也是通过shallowReactive,所以通过shallowRef创建的数据要监听.value的变化