【基础篇】Vue3中 ref 和 reactive 使用时的一些容易被忽视的细节注意点

258 阅读3分钟

ref 可以定义基本类型和对象类型

  • ref传入的数据会被包裹成一个对象(如下伪代码),value值为对应传入的数据,js中通过.value的形式来读取或修改数据,模板中则不需要。
    (只要改变value即可触发试图更新依赖收集
// 伪代码,不是真正的实现
const myRef = {
  _value: 0,
  get value() {
    track()
    return this._value
  },
  set value(newValue) {
    this._value = newValue
    trigger()
  }
}

reactive 只能定义对象类型(对象、数组和如 Map、Set 这样的集合类型)

  • 关于解构:通过解构或者直接取值的方式得到reactive对象身上的值并赋值给普通的变量,这样的变量是不具备响应式的,也就是普通的值(reactive的属性即使是ref,解构出来的也是普通的值)
    注意函数传参 解构取值

  • 一个 ref 会在作为响应式对象reactive(object)的属性被访问或修改时自动解包,融为一体(不需要.value),如果ref改变了,那么对应响应式对象的属性也会改变,反之也是一样的。
    注意: 普通对象和浅层次响应式对象则不会自动解包,Map,Array 也不会自动解包只是简单的赋值操作

<script lang="ts" setup>
import { ref, reactive, shallowReactive } from 'vue'


  const num1Ref = ref(0)
  const stateRt = reactive({ num1: num1Ref  })  // 响应式对象

  const num2Ref = ref(100)
  const stateSRt = shallowReactive({ num2: num2Ref})  // 浅层次响应式对象

  const num3Ref = ref(1000)
  const state = { num3: num3Ref }  // 普通对象



  const changeOp = () => {
    stateRt.num1++
    stateSRt.num2.value++
    state.num3.value ++

    console.log(num1Ref)   // RefImpl { ... _value: 1 ... }
    console.log(stateRt.num1)  // 1

    console.log(num2Ref)  // RefImpl { ... _value: 101 ... }
    console.log(stateSRt.num2) // RefImpl { ... _value: 101 ... }
    
    console.log(num3Ref)  // RefImpl { ... _value: 1001 ... }
    console.log(state.num3)  // RefImpl { ... _value: 1001 ... }
  }
</script>

<template>
  <button @click="changeOp">Click</button>
  <div>num1Ref: {{ num1Ref }}</div>
  <div>stateRt: {{ stateRt.num1 }}</div>
  ------------------------------------------
  <div>num2Ref: {{ num2Ref }}</div>
  <div>stateSRt: {{ stateSRt.num2 }}</div>
  ------------------------------------------
  <div>num3Ref: {{ num3Ref }}</div>
  <div>state: {{ state.num3 }}</div>
</template>
  • 要想改变二者的响应式的联动性,很简单。对于浅层次响应式对象和普通对象来说直接将对应的属性值改变即可
  const num2Ref = ref(100)
  const stateSRt = shallowReactive({ num2: num2Ref})  // 浅层次响应式对象

  const num3Ref = ref(1000)
  const state = { num3: num3Ref }  // 普通对象

  const num4Ref = ref(10000)

  const changeOp = () => {
    num2Ref.value++
    stateSRt.num2 = 'change' as any // 改变属性 这里类型转换粗暴一点

    console.log(num2Ref)  // RefImpl { ... _value: 101 ... } 
    console.log(stateSRt.num2)  // 'change'

    num3Ref.value++
    state.num3 = num4Ref  // 改变属性
    
    console.log(num3Ref)  // RefImpl { ... _value: 1001 ... }
    console.log(state.num3)  // num4Ref RefImpl { ... _value: 10000 ...}
  }
  • 对于响应式对象来说,要想改变二者的响应式的联动性,其实也很简单,但是区别就在于存在自动解包,如果直接改属性值相当于在改refvalue值,所以直接用新的ref来代替旧的ref赋值给属性即可
  const num1Ref = ref(0)
  const stateRt = reactive({ num1: num1Ref  })  // 响应式对象
  const changeOp = () => {
    num1Ref.value++ 
    stateRt.num1 = ref('new ref') as any  // 脱离组织
    console.log(num1Ref)   // RefImpl { ... _value: 1 ... }
    console.log(stateRt.num1)  // 'new Ref'
  }

  • 关于ref在模板表达式中的解包

只要表达式最终的计算值是一个ref,那么就会自动解包,文本插值的一个便利特性

<script setup lang="ts">
  const myRef = ref(99)
  const ctRt = reactive({
    num: ref(99)  // 自动解包
  })
  const ct = {
    num: ref(99)  // 普通对象
  }
</script>
<template>
  <!-- 99 -->
  <div>{{ myRef }}</div>
  <!-- 99 -->
  <div>{{ ctRt.num }}</div>
  <!-- 99 -->
  <div>{{ ct.num }}</div>
</template>

当ref需要在模板中运算时,顶层ref可以正常得到值,非顶层需要解构成顶层才能得到值

<script setup lang="ts" >
  const myRef = ref({  // 顶层ref
    foo: {
        bar: {
           num: 99
        }
    }  
  })
  const ctRt = reactive({
    num: ref(99)  // 自动解包
  })
  const ct = {  // 普通对象
    foo: {
        bar: {
           num: ref(99) 
        }
    }  
  }
  const { foo: { bar: { num} } } = ct  // 将普通对象的ref解构到顶层  --> 相当于 const num = ct.foo.bar.num
</script>
<template>
  <!-- 显示 100 顶层ref-->
  <div>{{ myRef.foo.bar.num + 1 }}</div>  
  <!-- 显示 100 reactive(自动解包)-->
  <div>{{ ctRt.num + 1}}</div>
  <!-- 显示 [object Object]1  这里演示代码 用any类型  -->
  <div>{{ (ct.foo.bar.num as any) + 1 }}</div>  
  <!-- 显示 100 上面解构之后运算 -->
  <div>{{ num + 1 }}</div>
</template>

结语

后面有时间的话可能会出一篇关于refreactive 的实现原理,敬请期待!