Vue3响应式核心API 使用注意点

1,和 vue2 的对比

vue2 的响应式原理通过 defineProperty 实现,vue3 通过 Proxy 实现。

但这里不讨论原理,讨论的是 vue3 新增 API 的使用和注意点。因为 vue3 将响应式 API 暴露了出来,是一套独立的数据响应式系统,和组件没有关系,这样也就能够实现单一状态管理

  • vue2 的响应式数据放在 data(){} 中,最终会被注入到组件实例上。
  • vue3 的响应式数据,是通过暴露出的响应式API来实现的,最终通过 setup() 返回。

2,核心 API 介绍

vue3 中的响应式数据,有2种格式:

  1. reactive 返回的 Proxy 对象,可直接访问属性。
  2. refcomputed 返回的 Ref 对象,需要通过 .value 访问属性。

1,reactive 和 readonly

官网参考

先说几点重要的:

  1. 都只能用于对象类型。
  2. 返回的代理对象和原始对象不相等。
  3. vue3 为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身(下面有例子说明)。
  4. readonly 的唯一区别是返回的代理对象是只读的,修改属性会报错。

看下面的例子

import {
    
     reactive, readonly } from 'vue'

const origin = {
    
     a: 1, b: 2 }
const state = reactive(origin)
console.log(state === reactive(origin)) // true 相同的代理对象
console.log(state === reactive(state)) // true 返回自身

const stateOnly = readonly(state)
console.log(stateOnly === state) // false

state.a++
console.log(stateOnly.a) // 2

虽然 stateOnly 是只读的。但因为代理的是 state,所以当 state 被修改时 stateOnly 也会被修改。

另外,stateOnly 代理–> state 代理–> { a: 1, b: 2 },所以 stateOnly !== state

简单应用

import {
    
     readonly, reactive } from 'vue'

/**
 * 返回1个对象和2个方法,
 * 对象是是响应式的,不允许直接修改。只能通过提供的方法修改指定属性。
 * @returns Object
 */
function useUser() {
    
    
  const userOrigin = reactive({
    
    })
  const user = readonly(userOrigin)
  const setUserName = (name) => {
    
    
    userOrigin.name = name
  }
  const setUserAge = (age) => {
    
    
    userOrigin.age = age
  }
  return {
    
    
    user,
    setUserName,
    setUserAge
  }
}

const {
    
     user, setUserName, setUserAge } = useUser()

console.log(user)
setUserName('下雪天的夏风')
setUserAge(18)
console.log(user)

2,ref

官网参考

可以代理任何数据,因为它是把这些数据都放到了一个对象的 .value 属性上,并返回这个对象。

注意,这个对象不是 Proxy 对象,而是 Ref 对象。区别在上面已经说明了。

如果 ref() 的参数是Proxy 对象,则直接将它放到 .value 属性上。

const state = reactive({
    
     a: 1, b: 2 })
const stateRef = ref(state)

console.log(stateRef.value === state) // true

使用 .value 的原因是,无法直接检测到普通变量的访问或修改。所以通过 gettersetter 方法来拦截对象属性的 get 和 set 操作。

ref 的大致原理如下:ref 原理参考

const myRef = {
    
    
  _value: 0,
  get value() {
    
    
    // 触发依赖收集,重新渲染
    track()
    return this._value
  },
  set value(newValue) {
    
    
    this._value = newValue
    // 通知依赖它的对象更新
    trigger()
  }
}

3,监听数据

watchEffect

官网参考

这个函数会立即运行,同时响应式地追踪其依赖,并在依赖更改时重新执行。

也就是说,可以同时监听多个。只要该函数中使用到的响应式数据被修改了,这个函数就会重新执行。

在 vue3 中,大多数情况下监听数据使用 watchEffect() 就够了。

watch

官网参考

watch 的使用方式和 vue2 中差别不大,和 watchEffect 的区别是:

  1. watch 默认是懒侦听,即仅在侦听源发生变化时才执行回调函数,可以指定配置项 { immediate: true }来立即执行一次。
  2. 可以获取到旧值。
  3. 可以更加明确的知道,是哪个状态的修改导致了 watch 的重新执行。

注意点:

watch 不能监听普通数据,监听来源只能是下面4种:

  1. 一个函数,返回一个值
  2. 一个 ref
  3. 一个响应式对象
  4. 由以上类型的值组成的数组

看下面的例子:

import {
    
     reactive, watch } from 'vue'

const state = reactive({
    
     a: 1, b: 2 })
watch(state.a, () => {
    
    
  console.log('变化了')
})

state.a++ // watch 函数并不会执行。

state.a 是普通的数据,不是响应式的,所以无法监听。

import {
    
     reactive, watch } from 'vue'

const state = reactive({
    
     a: 1, b: 2 })
watch(() => state.a, () => {
    
    
  console.log('变化了')
})

state.a++ // 变化了

如果 watch 的第一个参数是函数,则会调用该函数来收集依赖。也就是说,用到的 state.a 中的 state 是响应式数据,所以能被监听到了。

但如果是监听的是 const count = ref(0) ,就可以直接监听 count 而不使用函数的方式返回。因为传递的是对象。所以可以监听到 value 的值。

4,判断和转换

判断

API 含义
isProxy 判断数据是否reactivereadonly 创建的
isReactive 判断数据是否reactive创建的
isReadonly 判断数据是否readonly创建的
isRef 判断数据是否是ref对象

转换(toRefs)

更多参考工具函数

1,unref()

如果参数是 ref,则返回内部值,否则返回参数本身。

val = isRef(val) ? val.value : val

2,toRefs

这个比较重要。

toRefs 会将一个响应式对象的所有属性转换为 ref 格式,然后包装到一个普通对象中返回。

看下面的例子:

import {
    
     reactive, toRefs } from 'vue'

const state = reactive({
    
     a: 1, b: 2 })
const stateRef = toRefs(state)
console.log(stateRef) // {a: refObj, b:refObj},所以将 stateRef 展开也是响应式的。
console.log(stateRef.a.value) // 1

应用1

当父级传递的数据比较多,但只想用其中的某个,并且需要保持响应式:

<template>
  <h1>{
   
   { msg }}</h1>
  <h1>{
   
   { msg2Alias }}</h1>
</template>

<script setup>
import {
      
       toRef, toRefs, computed } from "vue";
const props = defineProps(["msg", "msg2"]);

// 下面3种方式等价,父级修改 msg2 时,msg2Alias会同步修改。
const {
      
        msg2: msg2Alias } = toRefs(props);
const msg2Alias = toRef(props, "msg2");
const msg2Alias = computed(() => props.msg2);
</script>

应用2

当使用方法返回的是代理对象时,可以解构而不失去响应式

import {
    
     reactive, toRefs } from "vue";

// composition function
function usePos(){
    
    
  const pos = reactive({
    
    x:0, y:0});
  return pos;
}

setup(){
    
    
  const {
    
    x, y} = usePos(); // lost reactivity
  const {
    
    x, y} = toRefs(usePos()); // reactivity
}

以上。

猜你喜欢

转载自blog.csdn.net/qq_40147756/article/details/134267664