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

2024-11-18 22:18:40 +0800 CST views 771

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

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

概述

watchwatchEffect 是 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 实例
    使用 getterjob 创建 ReactiveEffect 实例。

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

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

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

示例代码

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

推荐文章

JavaScript 上传文件的几种方式
2024-11-18 21:11:59 +0800 CST
对多个数组或多维数组进行排序
2024-11-17 05:10:28 +0800 CST
Elasticsearch 文档操作
2024-11-18 12:36:01 +0800 CST
使用Ollama部署本地大模型
2024-11-19 10:00:55 +0800 CST
支付轮询打赏系统介绍
2024-11-18 16:40:31 +0800 CST
服务器购买推荐
2024-11-18 23:48:02 +0800 CST
页面不存在404
2024-11-19 02:13:01 +0800 CST
使用xshell上传和下载文件
2024-11-18 12:55:11 +0800 CST
使用Vue 3实现无刷新数据加载
2024-11-18 17:48:20 +0800 CST
HTML + CSS 实现微信钱包界面
2024-11-18 14:59:25 +0800 CST
Python设计模式之工厂模式详解
2024-11-19 09:36:23 +0800 CST
Nginx 防盗链配置
2024-11-19 07:52:58 +0800 CST
Rust开发笔记 | Rust的交互式Shell
2024-11-18 19:55:44 +0800 CST
MySQL设置和开启慢查询
2024-11-19 03:09:43 +0800 CST
PostgreSQL日常运维命令总结分享
2024-11-18 06:58:22 +0800 CST
Java环境中使用Elasticsearch
2024-11-18 22:46:32 +0800 CST
纯CSS绘制iPhoneX的外观
2024-11-19 06:39:43 +0800 CST
阿里云免sdk发送短信代码
2025-01-01 12:22:14 +0800 CST
程序员茄子在线接单