响应式计算和侦听

    有时我们需要依赖于其他状态的状态——在 Vue 中,这是用组件处理的,以直接创建计算值,我们可以使用 computed 方法:它接受 getter 函数并为 getter 返回的值返回一个不可变的响应式 ref 对象。

    或者,它可以使用一个带有 getset 函数的对象来创建一个可写的 ref 对象。

    1. const count = ref(1)
    2. const plusOne = computed({
    3. get: () => count.value + 1,
    4. set: val => {
    5. count.value = val - 1
    6. }
    7. })
    8. plusOne.value = 1
    9. console.log(count.value) // 0

    为了根据反应状态自动应用重新应用副作用,我们可以使用 watchEffect 方法。它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

    1. const count = ref(0)
    2. watchEffect(() => console.log(count.value))
    3. // -> logs 0
    4. setTimeout(() => {
    5. count.value++
    6. // -> logs 1
    7. }, 100)

    watchEffect 在组件的 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。

    在一些情况下,也可以显式调用返回值以停止侦听:

    清除副作用

    有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

    • 副作用即将重新执行时
    • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 ,则在组件卸载时)
    1. watchEffect(onInvalidate => {
    2. const token = performAsyncOperation(id.value)
    3. onInvalidate(() => {
    4. // id has changed or watcher is stopped.
    5. // invalidate previously pending async operation
    6. token.cancel()
    7. })
    8. })

    在执行数据请求时,副作用函数往往是一个异步函数:

    1. const data = ref(null)
    2. watchEffect(async onInvalidate => {
    3. onInvalidate(() => {...}) // 我们在Promise解析之前注册清除函数
    4. data.value = await fetchData(props.id)
    5. })

    我们知道异步函数都会隐式地返回一个 Promise,但是清理函数必须要在 Promise 被 resolve 之前被注册。另外,Vue 依赖这个返回的 Promise 来自动处理 Promise 链上的潜在错误。

    Vue 的响应式系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick” 中多个状态改变导致的不必要的重复调用。在核心的具体实现中,组件的 update 函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时,默认情况下,会在所有的组件 update 执行:

    在这个例子中:

    • count 会在初始运行时同步打印出来
    • 更改 count 时,将在组件更新前执行副作用。

    如果需要在组件更新重新运行侦听器副作用,我们可以传递带有 flush 选项的附加 options 对象 (默认为 'pre'):

    1. // fire before component updates
    2. watchEffect(
    3. /* ... */
    4. },
    5. {
    6. flush: 'post'
    7. }

    flush 选项还接受 sync,这将强制效果始终同步触发。然而,这是低效的,应该很少需要。

    侦听器调试

    • 当响应式 property 或 ref 作为依赖项被追踪时,将调用 onTrack
    • 当依赖项变更导致副作用被触发时,将调用 onTrigger

    这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。建议在以下回调中编写 debugger 语句来检查依赖关系:

    1. watchEffect(
    2. () => {
    3. /* 副作用 */
    4. },
    5. {
    6. onTrigger(e) {
    7. debugger
    8. }
    9. }
    10. )

    onTrackonTrigger 只能在开发模式下工作。

    watch API 完全等同于组件 property。watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况下,它也是惰性的,即只有当被侦听的源发生变化时才执行回调。

    • watchEffect 比较,watch 允许我们:

      • 懒执行副作用;
      • 更具体地说明什么状态应该触发侦听器重新运行;
      • 访问侦听状态变化前后的值。

    侦听器数据源可以是返回值的 getter 函数,也可以直接是 ref

    侦听多个数据源

    侦听器还可以使用数组同时侦听多个源:

    1. watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
    2. /* ... */