第七节:带你全面理解vue3: 其他响应式进阶API

前言:

针对vue3官网中, 响应式:进阶API 中, 我们在上一章中给大家讲解了shallowRef, shallowReactive, shallowReadonly几个API的使用.

本章主要对剩下的API 进行讲解, 我们先看一下官网中进阶API 都有那些

img

对于剩下这些API, 你需要了解他们创建目的, 是为了解决之前的API存在的那些痛点问题, 这样你就能更好的了解使用他们的细节.工作中就可以有的放矢的选择不同的API.

1. triggerRef

我们首先来分析一下triggerRefAPI 的使用

1.1. triggerRef 针对的痛点问题

我们先看一个痛点问题:

对于ref响应式数据的变化, vue帮我们处理副作用. 比如,页面的更新, watchEffect侦听器回调函数的调用等.

但对于浅层响应数据, 比如shallowRef创建的数据, 其深层并不具有响应性, 也就是说vue并没有监测这些数据的变化, 当对深层数据进行修改时, 并不会触发副作用, 比如页面不会自动刷新.

triggerRefAPI 就是为了解决shallowRef浅层响应式数据深层修改问题.

当深层修改时, 会强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。

1.2. triggerRef 类型

类型:

function triggerRef(ref: ShallowRef): void

triggerRefAPI 函数接收一个shallowRefAPI 创建的数据, 作用就是强制触发这个浅层ref数据的副作用.

1.3. triggerRef 使用示例

示例:

<template>
  <div>
    <h3>shallowReadonly</h3>
    <div>{{ count }}</div>
    <div>{{ count2 }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, readonly, ref, shallowReadonly, shallowRef, triggerRef, watchEffect } from 'vue'

export default defineComponent({
  setup() {
    const count = ref({ num: 0 })
    const count2 = shallowRef({ num: 0 })


    // 对于ref 数据, 是深层响应式,
    // 因此当我们通过count.value.num++ 修改数据时,依然会触发watchEffect副作用函数
    watchEffect(() => {
      console.log('count.value.num', count.value.num)
    })


    // 因为shallowRef 数据不是深层响应式, 只有.value 整体修改才会触发响应式
    // 因为当我们通过count2.value.num++ 修改数据时,不会出发watchEffect 副作用函数
    // 同时视图也不会发生更改
    watchEffect(() => {
      console.log('count2.value.num', count2.value.num)
    })


    // 修改数据
    const change = () => {
      // count.value.num++
      count2.value.num++

      // 如果希望shallowRef 深层数据修改后,触发视图更新
      // 那么就需要使用triggerRef 手动触发更新
      triggerRef(count2)  // 手动更新count2
    }
    return { count, count2, change }
  }
})
</script>

通过示例的运行结果, 你也可以看出. shallowRef创建响应式数据, 在深层数据发生变化时, 不会触发页面更新 和watchEffect的处理函数. 因为深层不具有响应性.

当我们手动调用triggerRef函数, 并将shallowRef创建数据作为参数, 就是告诉vue , 我们需要强制执行shallowRef数据的副作用. 此时页面将会更新, watchEffect处理函数也会自动执行

1.4. triggerRef 使用小结

在理解triggerRefAPI 的使用后, 针对该API, 我做了以下小结

  • triggerRef常与shallowRef搭配使用
  • triggerRef会强制更新以shallowRef数据作为依赖的副作用,ref数据会自动触发这些副作用

我们需要注意的是: vue3只提供了triggerRef这个方法,但没有提供triggerReactive的方法。 也就是说triggerRef 【不可以】去更改 shallowReactive创建的数据

2. toRaw

根据一个 Vue 创建的代理返回其原始对象

2.1. toRaw 针对的问题

vue3中, 我们通过 reactive()readonly()shallowReactive() shallowReadonly()四个API 创建的响应式数据, 本质上就是通过Proxy创建的代理对象.

但有时我们在做数据传输时, 我们并不需要传响应式数据, 我们只想传最基本的原始对象.

toRawAPI 的作用就是返回 reactive()readonly()shallowReactive(),shallowReadonly() 创建的代理对应的原始对象。

2.2. toRaw 类型

toRaw 函数签名

function toRaw<T>(proxy: T): T

toRawAPI 函数接收一个Proxy代理对象(响应式对象)作为参数,

2.3. toRaw 使用示例

示例:

<template>
  <div>
    <h3>shallowReactive</h3>
    <div>{{ user }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRaw } from 'vue'

export default defineComponent({
  setup() {
    // 代理目标对象
    const obj = { name: '张三', age: 18 }

    // reactive 处理的代理对象
    const user = reactive(obj)

    // 控制触发代理对象
    console.log('user', user)

    // 使用toRaw, 参数是代理对象, 返回代理对象的目标对象
    console.log('toRaw(user)', toRaw(user))
    console.log('toRaw(user) === obj', toRaw(user) === obj)  // true

    // 修改数据
    const change = () => {
      user.name = '李四'

    }
    return { user, change }
  }
})
</script>

通过控制台输出结果, 你可以看出, toRaw 就是获取代理对象的原目标对象.

这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。

这句话来自于官网, 这句话你可以这么理解,

代理对象具有响应性, 可以理解为vue在监测这个数据的变化, 这个监测会消耗性能. 如果你的操作不要触发副作用, 就没有必要 使用具有响应性的代理对象.

比如调用接口时传入的参数, 就可以使用toRaw去掉代理对象的外壳, 获取到原始对象传入接口.

3. markRaw

markRaw 函数的作用就是将一个对象转为不可代理对象.

如果使用reactiveAPI , 也不会代理markRaw函数返回的对象, 会直接返回原对象.

示例:

<template>
  <div>
    <h3>shallowReactive</h3>
    <div>{{ user }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, markRaw, reactive } from 'vue'

export default defineComponent({
  setup() {
    // 代理目标对象
    const obj = { name: '张三', age: 18 }

    // 将obj原始对象标记为不可代理
    const markObj = markRaw(obj)

    // reactive 处理的代理对象
    const user = reactive(markObj)

    // user 不是代理对象
    console.log('user', user)


    // 修改数据
    const change = () => {
      user.name = '李四'

    }
    return { user, change }
  }
})
</script>

控制台输出:

img

通过控制台输出结果, 可以看出, 通过markRaw 处理过的对象具有一个__v_skip的属性, 用于标记这个对象不能创建代理对象, 即响应式数据.

尽管你将该对象传入reactive, 返回的也不是一个代理对象, 而是原对象.

既然不是响应数据,修改user.name 时, 就不会触发视图更新

该API的作用就是, 帮助你给一些你不希望创建为代理对象的原始对象添加标记.

4. effectScope

4.1. effectScope 作用

vue3的使用过程中,我们可能会针对同一个响应式数据创建多个副作用.比如computed, watch, watchEffect等.

再次过程中, 如果关闭某个副作用, 比如watch创建的侦听器, 就需要通过返回值关闭. 那么多个副作用你就需要一个一个关闭. 使用相对麻烦

effectScope字面意思就是副作用作用域, 可以理解为, 该函数创建一个作用域, 将所有的副作用放在共同一个作用域中, 如果以后想统一关闭副作用, 就可以使用作用域整体关闭.

4.2. effectScope

类型

function effectScope(detached?: boolean): EffectScope

interface EffectScope {
  run<T>(fn: () => T): T | undefined // 如果作用域不活跃就为 undefined
  stop(): void
}

effectScope函数返回一个作用域对象, 即EffectScope类型.

该作用域对象上具有run, stop方法, 同时run方法接收一个回调函数作为参数.

4.3. effectScope 使用方式

通过effectScope函数创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。

示例:

<template>
  <div>
    <h3>shallowReactive</h3>
    <div>{{ count }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, effectScope, markRaw, reactive, ref, shallowReactive, toRaw, watch, watchEffect } from 'vue'

export default defineComponent({
  setup() {
    // 创建ref 数据
    const count = ref(10)

    // 创建副作用作用域
    const scope = effectScope()
    // 控制台输出 effect 作用域
    console.log("scope", scope);
    
    // 收集运行的副作用
    scope.run(() => {
      // 计算属性副作用
      const computedCount = computed(() => count.value * 2
      )

      // watch 侦听副作用
      watch(
        count,
        () => {
          console.log('computedCount', computedCount.value)
          console.log('watch count', count.value)
        }
      )

      // watchEffect 副作用
      watchEffect(() => {
        console.log('watchEffect count', count.value)
      })

    })


    console.log('scope', scope) 
    // 2秒以后关闭所有的副作用
    setTimeout(() => {
      scope.stop()
    }, 2000)
    
    // 修改数据
    const change = () => {
      count.value++

    }
    return { count, change }
  }
})
</script>

控制台输出结果:

img

通过控制台输出的effect作用域对象, 你可以看到, 作用域将回调函数中的副作用进行了收集, 存储在effects属性上.

同时effect作用域对象原型对象上具有run收集副作用的方法, stop关闭副作用的方法.

5. getCurrentScope

getCurrentScope函数返回当前活跃的 effect 作用域。

在前一个API中, 给大家讲解了effectScope函数, 该函数执行后会返回一个effect 作用域, 通过调用effect作用域对象的run方法收集所有副作用. 我们就可以在run方法的回调函数中, 通过getCurrentScope函数获取到正在活跃的effect作用域对象.

示例:

// 创建副作用作用域
const scope = effectScope();
console.log("scope", scope);

// 收集运行的副作用
scope.run(() => {
  // 计算属性副作用
  const computedCount = computed(() => count.value * 2);

  // watch 侦听副作用
  watch(count, () => {
    console.log("computedCount", computedCount.value);
    console.log("watch count", count.value);
  });

  // watchEffect 副作用
  watchEffect(() => {
    console.log("watchEffect count", count.value);
  });

  // 通过  getCurrentScope() 获取当前真正活跃的 effect 作用域对象
  const effectScope = getCurrentScope();

  console.log("getCurrentScope", effectScope === scope);
  // 控制台输出结果: getCurrentScope true
});

示例中, 我们通过effectScope创建了一个effect作用域对象, 当调用该作用域对象的run方法,传入回调函数, 会自动执行回调函数, 收集副作用, 并将收集到的副作用保存在副作用effect作用域中. 也就是说, 在执行回调函数时, 我们创建的scope就是活跃的effect作用域

之后,我们通过执行getCurrentScope函数获取当前活跃的副作用作用域, 和之前我们创建的作用域对比, 发现getCurrentScope 获取的就是我们创建的effect作用域.

其实每一个组件都有一个effect作用域, 用于收集组件内所有的副作用. 组件更新函数本身也就是一个副作用. 这也就是响应式数据变化后, 页面会重新渲染的原因.

以及组件被销毁后, vue3 会通过组件的effect作用域清理组件内收集的所有副作用

该API 在工作中并不常使用到. 甚至一个项目里连一次都不会用到.

6. onScopeDispose

该API 函数主要用于调试, 工作中也不怎么常用, 其作用就是在当前活跃的副作用(effect)作用域对象上注册一个调试的回调函数. 在effect作用域关闭时, 会自动调用注册的回调函数,.

示例:

// 创建副作用作用域
const scope = effectScope();
console.log("scope", scope);

// 收集运行的副作用
scope.run(() => {
  // 计算属性副作用
  const computedCount = computed(() => count.value * 2);

  // watch 侦听副作用
  watch(count, () => {
    console.log("computedCount", computedCount.value);
    console.log("watch count", count.value);
  });

  // watchEffect 副作用
  watchEffect(() => {
    console.log("watchEffect count", count.value);
  });

  // 在当前活跃的 effect 作用域对象上注册一个回调函数
  onScopeDispose(() => {
    console.log("当前effectScope 停止");
  });
});

// 2秒以后关闭所有的副作用
setTimeout(() => {
  scope.stop();
}, 2000);

示例中, 我们在effectScope收集副作用时, 通过onScopeDispose函数注册了一个回调函数.

effectScope副作用作用域, 即scope对象调用stop方法时, 会自动执行注册的回调函数. 多用于功能调试

7. 结语

至此, 就把vue3中响应式进阶API 中剩余的API函数给大家讲完了, 这里比较常用的API 有triggerRef, toRaw, markRaw, effectScope, 其余两个API 函数并不怎么常用.

这里尤其要注意effectScope, 使用好了可以给代码增色不少.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值