watchEffect 中的异步问题

你能否正确地使用 watchEffect 来追踪响应式依赖呢?

视频的倍数在 watchEffect 中并没有被收集,失效了,这是为什么?

大家好我是渡一前端子辰老师,这个问题来自一名同学反馈的,我们来看一下简化后的代码。

html 复制代码
<template>
  <div class="container">
    <video ref="videoRef" :src="url" controls></video>
    <!--  按钮调整倍率  -->
    <div class="btns">
      <button class="btn" :disabled="speed === 0.5" @click="speed -= 0.5">
        -0.5
      </button>
      <span class="speed">{{ speed.toFixed(1) }}</span>
      <button :disabled="speed === 3" class="btn" @click="speed += 0.5">
        +0.5
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, watchEffect } from 'vue';
import { fetchVideoUrl } from './api/video';

const speed = ref(1);
const url = ref('');
const videoRef = ref(null);

watchEffect(async () => {
  // 远程去获取 url 地址
  url.value = await fetchVideoUrl();
  // 通过 video 的 ref,拿到 video,设置倍率
  videoRef.value.playbackRate = speed.value;
  // 用于检查 watchEffect 执行
  console.log('0')
});
</script>

解题

我们先来回顾一下 watchEffect 的特性和用法。

Vue watchEffect 是一个用于追踪响应式依赖的函数。

它可以让你在一个函数中使用响应式的数据,例如 ref 或 reactive 创建的对象,然后当这些数据变化时,自动重新执行这个函数。

它相当于将watch的依赖源和回调函数合并,不需要指定具体要观察的数据。

可以看到 watchEffect 的特性就是数据变化重新执行。

可是问题就在于,当他改变 speed 的值的时候,watchEffect 并没有被执行,我们在 watchEffect 最后打印一下看看。

可以看到 watchEffect 仅在第一次执行了,之后改变数据并没有继续输出。

其实问题就在于 watchEffect 只能收集同步代码的依赖,如果 watchEffect 中有异步代码,当遇到异步时就会停止依赖的收集。

套用到我们的代码中来说,就是你运行 watchEffect 就会立即运行它包含的函数,但是代码中 url.value = await fetchVideoUrl(); 是异步代码且在第一行,所以 watchEffect 并没有收集到任何依赖。

问题我们知道了,但是这个代码写的就是有点问题的,为什么要在改变速率的时候要去重复请求 url 呢?这个只需要做一次就可以了。

可以改成这样。

html 复制代码
<script setup>
  import { ref, watchEffect } from 'vue';
  import { fetchVideoUrl } from './api/video';
  const speed = ref(1);
  const url = ref('');
  const videoRef = ref(null);
  (async () => {
    url.value = await fetchVideoUrl();
  })();
  watchEffect(async () => {
    videoRef.value.playbackRate = speed.value;
    console.log('0')
  });
</script>

我们将异步函数移除的话就可以实现 watchEffect 的正常收集了,我们看一下效果。

可以看到正常收集了。

我们回到原来的问题上,如果非要写异步在里边其实也是可以的,我们只要让 watchEffect 先收集一下同步代码就行。

html 复制代码
<script setup>
  import { ref, watchEffect } from 'vue';
  import { fetchVideoUrl } from './api/video';
  const speed = ref(1);
  const url = ref('');
  const videoRef = ref(null);
  watchEffect(async () => {
    speed.value;
    url.value = await fetchVideoUrl();
    videoRef.value.playbackRate = speed.value;
  });
</script>

只需要把同步代码提到异步之前就可以了。

效果也是正常的。

总结

其实这个问题如果不注意的会让你调试很久,至于 watchEffect 为什么不收集异步,因为 Vue 源码就是这样写的。

比如说你给 watchEffect 传了一个函数 A,它的做法很简单 new Watcher(A),Watcher 里边呢就是把 A 调用一次,它不管代码是异步还是同步。

所以说 A 运行,只运行了同步代码,剩下的异步代码还在微队列里等着呢。

那么只有在这个同步代码的运行期间才被收集了依赖。

如果说你们以后遇到了,watchEffect 里边需要在异步之后去收集依赖的话,就可以使用文章中的方法去解决一下。

本文来源

本文来源自渡一官方公众号:Duing ,欢迎关注,获取最新最全最深入的技术讲解

感谢你阅读本文,如果你有任何疑问或建议,请在评论区留言,如果你觉得这篇文章有用,请点赞收藏或分享给你的朋友

相关推荐
Mr_fang719406 分钟前
JS 面试 手写代码
前端·javascript
Mintopia7 分钟前
Three.js 多材质对象:给 3D 模型穿上 “混搭潮装”
前端·javascript·three.js
xiezhr19 分钟前
在trae的帮助下开发了俄罗斯方块游戏
前端·javascript·html
嘉小华23 分钟前
深入浅出Android ViewBinding
前端
嘉小华27 分钟前
深入浅出Android DataBinding
前端
赫本的猫27 分钟前
JavaScript作用域详解:从基础到欺骗词法
前端·javascript
星河丶28 分钟前
React 虚拟 DOM 的 Diff 算法原理
前端·react.js
赫本的猫28 分钟前
JavaScript 预编译机制深度解析
前端·javascript
啊~哈1 小时前
页面弹窗适配问题
前端·javascript·vue.js
工呈士1 小时前
构建优化策略:Tree Shaking、代码分割与懒加载
前端·面试