【Vue】2207- Vue 3 中的 Watch 实现及最佳实践

科技   2024-09-30 22:20   福建  
  • 作者:陈大鱼头
  • github: https://github.com/KRISACHAN

在 Vue 3 中,watchwatchEffectonWatcherCleanup 是三个非常重要的 API,用于响应式数据的监听和副作用处理。本文将详细解析它们的实现原理、使用方式以及最佳实践。

概述

watchwatchEffect 是 Vue 3 中用于监听响应式数据变化并执行副作用的两个主要 API,而 onWatcherCleanup 则用于在 watcher 被清理时执行清理逻辑。它们在处理异步操作、数据变化响应等场景中非常有用。

源码解析

文件位置

  • core/packages/runtime-core/src/apiWatch.ts

关键代码示例与注释

apiWatch.ts

apiWatch.ts 文件中,定义了 watchwatchEffect 的实现逻辑。

import { ReactiveEffect, track, trigger } from '@vue/reactivity';
import { queuePreFlushCb } from './scheduler';
import { EMPTY_OBJ, isFunction, isObject } from '@vue/shared';

// 定义 watch 函数
export function watch(source, cb, options?{
  return doWatch(source, cb, options);
}

// 定义 watchEffect 函数
export function watchEffect(effect, options?{
  return doWatch(effect, null, options);
}

// 核心的 doWatch 函数
function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ{
  let getter;
  if (isFunction(source)) {
    getter = source; // 如果 source 是函数,直接作为 getter
  } else {
    getter = () => source; // 否则创建一个返回 source 的函数
  }

  let cleanup;
  const onCleanup = (fn) => {
    cleanup = effect.onStop = () => {
      fn(); // 注册清理函数
    };
  };

  const job = () => {
    if (cleanup) {
      cleanup(); // 执行清理函数
    }
    if (cb) {
      cb(); // 执行回调函数
    } else {
      effect.run(); // 运行副作用
    }
  };

  const effect = new ReactiveEffect(getter, job);
  if (cb) {
    if (immediate) {
      job(); // 立即执行
    } else {
      effect.run(); // 否则运行副作用
    }
  } else {
    effect.run(); // 运行副作用
  }

  return () => {
    effect.stop(); // 停止副作用
  };
}

函数内部关键流程

  1. **定义 getter**:

  • 如果 source 是函数,则直接作为 getter
  • 否则,创建一个返回 source 的函数作为 getter
  • 定义清理函数

    • 使用 onCleanup 注册清理函数,在副作用停止时执行。
  • 定义 job 函数

    • job 函数中,先执行清理函数,然后执行回调函数或运行副作用。
  • 创建 ReactiveEffect 实例

    • 使用 getterjob 创建 ReactiveEffect 实例。
  • 执行副作用或回调

    • 如果有回调函数,根据 immediate 选项决定是否立即执行 job
    • 否则,运行副作用。
  • 返回停止函数

    • 返回一个函数,用于停止副作用。

    API 使用方式与参数

    watch

    watch 用于监听响应式数据的变化,并在变化时执行回调函数。

    参数

    • source:要监听的响应式数据或 getter 函数。
    • cb:数据变化时执行的回调函数。
    • options:可选参数对象,包括 immediatedeepflushonTrackonTrigger

    示例代码

    <template>
      <div>
        <input v-model="question" placeholder="Ask a question" />
        <p>{{ answer }}</p>
      </div>
    </template>

    <script>
    import { ref, watch } from 'vue';

    export default {
      setup() {
        const question = ref('');
        const answer = ref('Questions usually contain a question mark. ;-)');

        // 监听 question 的变化
        watch(question, (newQuestion, oldQuestion) => {
          if (newQuestion.includes('?')) {
            answer.value = 'Thinking...';
            // 模拟异步操作
            setTimeout(() => {
              answer.value = 'Yes';
            }, 1000);
          }
        });

        return {
          question,
          answer,
        };
      },
    };
    </script>

    watchEffect

    watchEffect 用于自动追踪其回调函数中使用的所有响应式数据,并在这些数据变化时重新执行回调函数。

    参数

    • effect:要执行的副作用函数。
    • options:可选参数对象,包括 flush

    示例代码

    <template>
      <div>
        <input v-model="question" placeholder="Ask a question" />
        <p>{{ answer }}</p>
      </div>
    </template>

    <script>
    import { ref, watchEffect } from 'vue';

    export default {
      setup() {
        const question = ref('');
        const answer = ref('Questions usually contain a question mark. ;-)');

        // 自动追踪 question 的变化
        watchEffect(() => {
          if (question.value.includes('?')) {
            answer.value = 'Thinking...';
            // 模拟异步操作
            setTimeout(() => {
              answer.value = 'Yes';
            }, 1000);
          }
        });

        return {
          question,
          answer,
        };
      },
    };
    </script>

    onWatcherCleanup

    onWatcherCleanup 用于在 watcher 被清理时执行清理逻辑。注意,onWatcherCleanup 仅在 Vue 3.5+ 中支持,且必须在 watchEffect 的副作用函数或 watch 的回调函数的同步执行期间调用:不能在异步函数中的 await 语句之后调用。

    参数

    • cleanupFn:要执行的清理函数。

    示例代码

    <template>
      <div>
        <input v-model="question" placeholder="Ask a question" />
        <p>{{ answer }}</p>
      </div>
    </template>

    <script>
    import { ref, watch, onWatcherCleanup } from 'vue';

    export default {
      setup() {
        const question = ref('');
        const answer = ref('Questions usually contain a question mark. ;-)');

        // 监听 question 的变化
        watch(question, (newQuestion, oldQuestion, onCleanup) => {
          if (newQuestion.includes('?')) {
            answer.value = 'Thinking...';
            const timeout = setTimeout(() => {
              answer.value = 'Yes';
            }, 1000);

            // 注册清理函数
            onCleanup(() => {
              clearTimeout(timeout);
            });
          }
        });

        return {
          question,
          answer,
        };
      },
    };
    </script>

    最佳实践示例

    深度监听

    使用 deep 选项来监听对象的嵌套属性变化。

    <template>
      <div>
        <input v-model="user.name" placeholder="Enter your name" />
        <p>{{ user.name }}</p>
      </div>
    </template>

    <script>
    import { reactive, watch } from 'vue';

    export default {
      setup() {
        const user = reactive({
          name'',
        });

        // 深度监听 user 对象的变化
        watch(user, (newUser, oldUser) => {
          console.log('User changed:', newUser);
        }, { deeptrue });

        return {
          user,
        };
      },
    };
    </script>

    深度监听(指定层级)

    在 Vue 3.5+ 中,deep 选项可以是一个数字,表示最大遍历深度。

    <template>
      <div>
        <input v-model="user.name" placeholder="Enter your name" />
        <p>{{ user.name }}</p>
      </div>
    </template>

    <script>
    import { reactive, watch } from 'vue';

    export default {
      setup() {
        const user = reactive({
          name'',
          address: {
            city'',
            country''
          }
        });

        // 深度监听 user 对象的变化,最大遍历深度为 2
        watch(user, (newUser, oldUser) => {
          console.log('User changed:', newUser);
        }, { deep2 });

        return {
          user,
        };
      },
    };
    </script>

    立即执行

    使用 immediate 选项在 watcher 创建时立即执行回调。

    <template>
      <div>
        <input v-model="question" placeholder="Ask a question" />
        <p>{{ answer }}</p>
      </div>
    </template>

    <script>
    import { ref, watch } from 'vue';

    export default {
      setup() {
        const question = ref('');
        const answer = ref('Questions usually contain a question mark. ;-)');

        // 立即执行回调
        watch(question, (newQuestion, oldQuestion) => {
          if (newQuestion.includes('?')) {
            answer.value = 'Thinking...';
            setTimeout(() => {
              answer.value = 'Yes';
            }, 1000);
          }
        }, { immediatetrue });

        return {
          question,
          answer,
        };
      },
    };
    </script>

    清理副作用

    使用 onWatcherCleanup 清理异步操作。

    <template>
      <div>
        <input v-model="question" placeholder="Ask a question" />
        <p>{{ answer }}</p>
      </div>
    </template>

    <script>
    import { ref, watch, onWatcherCleanup } from 'vue';

    export default {
      setup() {
        const question = ref('');
        const answer = ref('Questions usually contain a question mark. ;-)');

        // 监听 question 的变化
        watch(question, (newQuestion, oldQuestion, onCleanup) => {
          if (newQuestion.includes('?')) {
            answer.value = 'Thinking...';
            const timeout = setTimeout(() => {
              answer.value = 'Yes';
            }, 1000);

            // 注册清理函数
            onCleanup(() => {
              clearTimeout(timeout);
            });
          }
        });

        return {
          question,
          answer,
        };
      },
    };
    </script>

    总结

    在 Vue 3 中,watchwatchEffectonWatcherCleanup 是处理响应式数据变化和副作用的重要工具。通过理解它们的实现原理和使用方式,可以更好地管理应用中的数据变化和副作用处理,提升开发效率和代码质量。

    参考资料

    • Vue.js 官方文档 - Watchers
    • Vue.js 源码 - apiWatch.ts

    前端自习课
    每日清晨,享受一篇前端优秀文章。
     最新文章