Vue 3 响应式侦听与计算

988 阅读3分钟

响应式侦听和计算

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

我们先来看看一个简单的例子,关于计算值的方式,同样我们在 src/TemplateM.vue 写下如下代码:

<template>
  <div class="template-m-wrap">
    count ---> {{count}}
    plusOne ---> {{plusOne}}
  </div>
</template>
<script>
import { ref, defineComponent, reactive, computed } from "vue";
export default defineComponent({
  name: 'TemplateM',
  setup() {
    let count = ref(2)
    let plusOne = computed(() => {
      return count.value++
    })
    console.log(plusOne.value)
    return {
      count,
      plusOne
    }
  }
})
</script>

访问链接效果如下:

我们可以看到 plusOne 没有值。或者,它可以使用一个带有 getset 函数的对象来创建一个可写的 ref 对象。

<template>
  <div class="template-m-wrap">
    count ---> {{ count }} plusOne ---> {{ plusOne }}
  </div>
</template>
<script>
import { ref, defineComponent, reactive, computed } from "vue";
export default defineComponent({
  name: "TemplateM",
  setup() {
    let count = ref(2);
    let plusOne = computed({
      get() {
        return count.value++;
      },
      set(val) {
        count.value = val;
      },
    });
    plusOne.value = 1;
    console.log(count.value); // 0
    return {
      count,
      plusOne,
    };
  },
});
</script>

同样访问效果如下:

watchEffect

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

<template>
  <div class="template-m-wrap">
    count ---> {{ count }}
  </div>
</template>
<script>
import { ref, defineComponent, reactive, computed, watchEffect } from "vue";
export default defineComponent({
  name: "TemplateM",
  setup() {
    let count = ref(2);
    watchEffect(() => {
      console.log(count.value)
    })
    setTimeout(() => {
      count.value++
    }, 1000)
    return {
      count,
    };
  },
});
</script>

查看效果如下:

停止侦听

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

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

<template>
  <div class="template-m-wrap">
    count ---> {{ count }}
  </div>
</template>
<script>
import { ref, defineComponent, reactive, computed, watchEffect } from "vue";
export default defineComponent({
  name: "TemplateM",
  setup() {
    let count = ref(2);
    const stopWatch = watchEffect(() => {
      console.log(count.value)
    })
    stopWatch()
    setTimeout(() => {
      count.value++
    }, 1000)
    return {
      count,
    };
  },
});
</script>

查看效果如下:

副作用刷新时机

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

<template>
  <div>{{ count }}</div>
</template>

<script>
  export default {
    setup() {
      const count = ref(0)

      watchEffect(() => {
        console.log(count.value)
      })

      return {
        count
      }
    }
  }
</script>

在这个例子中:

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

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

// fire before component updates
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'post'
  }
)

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

侦听器调试

onTrackonTrigger 选项可用于调试侦听器的行为。

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

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

watchEffect(
  () => {
    /* 副作用 */
  },
  {
    onTrigger(e) {
      debugger
    }
  }
)

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

watch

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

  • watchEffect 比较,watch 允许我们:
    • 懒执行副作用;
    • 更具体地说明什么状态应该触发侦听器重新运行;
    • 访问侦听状态变化前后的值。

侦听单个数据源

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

// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 直接侦听ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})