编程 Vue 3 中的 Watch 实现及最佳实践

2024-11-18 22:18:40

Vue 3 中的 Watch 实现及最佳实践

在 Vue 3 中,watch、watchEffect 和 onWatcherCleanup 是非常重要的 API,用于监听响应式数据的变化和处理副作用。本文将详细介绍它们的实现原理、使用方法以及最佳实践。

概述

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

源码解析

文件位置

核心代码在 core/packages/runtime-core/src/apiWatch.ts 中实现。

关键代码示例与注释

apiWatch.ts

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。
  2. 定义清理函数:
    使用 onCleanup 注册清理函数,在副作用停止时执行。

  3. 定义 job 函数:
    在 job 函数中,先执行清理函数,然后执行回调函数或运行副作用。

  4. 创建 ReactiveEffect 实例:
    使用 getter 和 job 创建 ReactiveEffect 实例。

  5. 执行副作用或回调:
    根据 immediate 选项决定是否立即执行 job 或运行副作用。

  6. 返回停止函数:
    返回一个函数,用于停止副作用。

API 使用方式与参数

watch

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

参数

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

示例代码

<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 被清理时执行清理逻辑。

示例代码

<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, 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);
    }, { deep: true });

    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) => {
      if (newQuestion.includes('?')) {
        answer.value = 'Thinking...';
        setTimeout(() => {
          answer.value = 'Yes';
        }, 1000);
      }
    }, { immediate: true });

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

总结

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

推荐文章

程序员茄子在线接单