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 ,欢迎关注,获取最新最全最深入的技术讲解

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

相关推荐
三氧化真13 分钟前
设计模式-装饰者模式
java·设计模式
zzzzzzzziu16 分钟前
vue3基础
前端·javascript·vue.js
Jasonakeke24 分钟前
【JavaWeb】二、HTML 入门
前端·html
2301_796143791 小时前
Vue的指令v-model的原理
前端·javascript·vue.js
anyup_前端梦工厂1 小时前
探索 Web Speech API:实现浏览器语音识别与合成
前端·javascript·html
xgq1 小时前
Wake Lock API:保持设备唤醒的利器
前端·javascript·面试
Jacky-YY1 小时前
Nginx-HTTP和反向代理web服务器
服务器·前端·nginx·http
软件技术NINI1 小时前
Vue 3 是 Vue.js 的下一代版本,它在许多方面都带来了显著的改进和变化,旨在提高开发效率和用户体验
前端·vue.js·ux
caperxi1 小时前
当 PC 端和移动端共用一个域名时,避免 CDN 缓存页面混乱(nginx)
前端·nginx·缓存
Book_熬夜!1 小时前
HTML和CSS做一个无脚本的手风琴页面(保姆级)
前端·css·平面·html·html5