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

前言:

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

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

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

1. triggerRef

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

1.1. triggerRef 针对的痛点问题

我们先看一个痛点问题:

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

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

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

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

1.2. triggerRef 类型

类型:

javascript 复制代码
function triggerRef(ref: ShallowRef): void

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

1.3. triggerRef 使用示例

示例:

xml 复制代码
<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 函数签名

r 复制代码
function toRaw<T>(proxy: T): T

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

2.3. toRaw 使用示例

示例:

xml 复制代码
<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函数返回的对象, 会直接返回原对象.

示例:

xml 复制代码
<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>

控制台输出:

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

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

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

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

4. effectScope

4.1. effectScope 作用

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

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

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

4.2. effectScope

类型

php 复制代码
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 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。

示例:

xml 复制代码
<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>

控制台输出结果:

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

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

5. getCurrentScope

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

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

示例:

javascript 复制代码
// 创建副作用作用域
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作用域关闭时, 会自动调用注册的回调函数,.

示例:

javascript 复制代码
// 创建副作用作用域
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, 使用好了可以给代码增色不少.

相关推荐
uwvwko15 分钟前
BUUCTF——web刷题第一页题解
android·前端·数据库·php·web·ctf
有事没事实验室43 分钟前
CSS 浮动与定位以及定位中z-index的堆叠问题
前端·css·开源
Stringzhua1 小时前
JavaScript入门【3】面向对象
javascript
2501_915373881 小时前
Vue路由深度解析:Vue Router与导航守卫
前端·javascript·vue.js
小妖6661 小时前
前端表格滑动滚动条太费事,做个浮动滑动插件
前端
读心悦1 小时前
5000 字总结CSS 中的过渡、动画和变换详解
前端·css·tensorflow
__BMGT()1 小时前
C++ QT 打开图片
前端·c++·qt
仍然探索未知中2 小时前
前端扫盲HTML
前端·html
Brilliant Nemo2 小时前
Vue2项目中使用videojs播放mp4视频
开发语言·前端·javascript