React实现Vue的watch和computed

你想知道在 React 中如何实现 Vue 里watch(监听数据变化)和computed(计算属性)的功能,核心是要找到 React 生态中对应的实现思路和具体写法 ------ 作为前端面试 / 开发中高频对比的知识点,下面我会分两部分清晰讲解,兼顾基础用法和进阶场景,让你能直接落地。

一、实现 Vue 的computed(计算属性)

Vue 的computed核心特点:基于依赖缓存,依赖不变时不会重复计算。在 React 中,实现这个核心逻辑的方式分两种(对应不同 React 版本):

1. 基础版(函数组件 + useMemo

useMemo是 React 官方提供的缓存计算结果的 Hook,完全匹配computed的 "缓存" 核心特性,是最常用的方式。

示例代码(模拟 Vue computed)

jsx

javascript 复制代码
import { useState, useMemo } from 'react';

function App() {
  // 模拟Vue的data
  const [num1, setNum1] = useState(1);
  const [num2, setNum2] = useState(2);
  const [unused, setUnused] = useState(0); // 无关依赖

  // 实现Vue的computed:计算两个数的和,仅依赖num1/num2
  const sum = useMemo(() => {
    console.log('计算sum(仅依赖变化时执行)');
    return num1 + num2;
  }, [num1, num2]); // 依赖数组:仅当num1/num2变化时,重新计算sum

  return (
    <div>
      <p>num1: {num1}</p>
      <button onClick={() => setNum1(num1 + 1)}>num1+1</button>
      <p>num2: {num2}</p>
      <button onClick={() => setNum2(num2 + 1)}>num2+1</button>
      <p>无关值: {unused}</p>
      <button onClick={() => setUnused(unused + 1)}>修改无关值</button>
      <p>计算属性sum: {sum}</p>
    </div>
  );
}

export default App;

核心解释

  • useMemo的第一个参数是计算逻辑(对应 Vue computed 的函数),第二个参数是依赖数组(对应 Vue computed 的隐式依赖)。
  • 点击 "修改无关值" 时,sum不会重新计算(控制台不打印),只有num1/num2变化时才会重新计算,完全匹配 Vue computed 的缓存特性。
  • 若不使用useMemo,直接写const sum = num1 + num2,则每次组件渲染(哪怕无关值变化)都会重新计算,失去缓存优势。
2. 简化版(仅简单计算,无需缓存)

如果计算逻辑极简单(比如单变量拼接),且不关心重复计算,可直接写普通变量(相当于 Vue computed 关闭缓存的场景):

jsx

javascript 复制代码
const [name, setName] = useState('React');
// 简单计算,无缓存(每次渲染都会执行)
const fullName = `Hello ${name}`;

二、实现 Vue 的watch(监听数据变化)

Vue 的watch核心特点:监听指定数据,数据变化时执行副作用(比如请求、修改其他数据)。React 中实现分 3 种场景,覆盖基础监听、深度监听、立即执行:

1. 基础监听(函数组件 + useEffect

useEffect是 React 处理副作用的核心 Hook,通过依赖数组控制执行时机,完美匹配基础版watch

示例代码(模拟 Vue watch 基础用法)

jsx

javascript 复制代码
import { useState, useEffect } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ name: '张三', age: 18 });

  // 场景1:监听单个基础类型值(对应Vue watch: { count: handler })
  useEffect(() => {
    console.log(`count变化了:新值=${count}`);
    // 可执行副作用:比如发请求、修改DOM、存储到本地等
    localStorage.setItem('count', count);
  }, [count]); // 依赖数组:仅count变化时执行

  // 场景2:监听对象的某个属性(对应Vue watch: { 'user.name': handler })
  useEffect(() => {
    console.log(`用户名变化了:新值=${user.name}`);
  }, [user.name]); // 依赖是user.name,仅该属性变化时执行

  return (
    <div>
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>count+1</button>
      <p>用户名: {user.name}</p>
      <button onClick={() => setUser({ ...user, name: '李四' })}>修改用户名</button>
      <button onClick={() => setUser({ ...user, age: 19 })}>修改年龄(不触发用户名监听)</button>
    </div>
  );
}
2. 深度监听(模拟 Vue watch 的deep: true

Vue 的deep: true用于监听对象 / 数组的深层变化,React 中需结合useEffect + 自定义对比(或useDeepCompareEffect)实现:

方案 1:手动监听所有深层属性(简单对象)

jsx

javascript 复制代码
// 监听user对象的所有属性(深度监听)
useEffect(() => {
  console.log('user对象深层变化了:', user);
}, [user.name, user.age]); // 列出所有深层属性

方案 2:使用自定义 Hook(复杂对象,推荐) 封装useDeepCompareEffect(基于lodash.isEqual做深层对比),适配复杂对象 / 数组:

jsx

javascript 复制代码
import { useState, useEffect, useRef } from 'react';
import isEqual from 'lodash.isEqual';

// 自定义深层对比的useEffect Hook
function useDeepCompareEffect(callback, dependencies) {
  const prevDeps = useRef();
  // 用lodash.isEqual对比新旧依赖(深层对比)
  if (!isEqual(prevDeps.current, dependencies)) {
    prevDeps.current = dependencies;
  }
  // 依赖改为prevDeps.current(只有深层变化时才更新)
  useEffect(callback, [prevDeps.current]);
}

// 使用示例
function App() {
  const [user, setUser] = useState({
    name: '张三',
    info: { age: 18, address: '北京' } // 深层对象
  });

  // 深度监听user对象
  useDeepCompareEffect(() => {
    console.log('user深层变化了:', user);
  }, [user]);

  return (
    <button onClick={() => setUser({
      ...user,
      info: { ...user.info, address: '上海' } // 修改深层属性
    })}>修改地址</button>
  );
}
3. 立即执行(模拟 Vue watch 的immediate: true

Vue 的immediate: true表示监听函数在组件初始化时立即执行一次,React 中只需把useEffect的依赖数组保留(默认首次渲染执行):

jsx

javascript 复制代码
useEffect(() => {
  console.log('count监听:初始化执行,后续变化也执行');
}, [count]); // 首次渲染 + count变化时执行(默认就是immediate: true效果)

// 若要取消"立即执行"(仅变化时执行):需加标识位
const [count, setCount] = useState(0);
const isFirstRender = useRef(true);
useEffect(() => {
  if (isFirstRender.current) {
    isFirstRender.current = false;
    return; // 首次渲染不执行
  }
  console.log('count变化了(仅变化时执行)');
}, [count]);
4. 进阶:使用useWatch自定义 Hook(封装成 Vue 风格)

为了更贴近 Vue 的watch写法,可封装通用的useWatch Hook,复用性更高:

jsx

javascript 复制代码
import { useEffect, useRef } from 'react';
import isEqual from 'lodash.isEqual';

// 封装Vue风格的useWatch Hook
function useWatch(watchSource, handler, options = { immediate: true, deep: false }) {
  const { immediate, deep } = options;
  const prevValue = useRef();

  useEffect(() => {
    // 立即执行
    if (immediate) {
      handler(watchSource, undefined); // 首次执行:新值=watchSource,旧值=undefined
    } else if (!isEqual(prevValue.current, watchSource)) {
      // 非首次:对比值变化(深层对比)
      handler(watchSource, prevValue.current);
    }

    // 保存当前值为旧值
    prevValue.current = deep ? JSON.parse(JSON.stringify(watchSource)) : watchSource;
  }, deep ? [JSON.stringify(watchSource)] : [watchSource]); // 深层监听用JSON.stringify
}

// 使用示例
function App() {
  const [user, setUser] = useState({ name: '张三', age: 18 });

  // 模拟Vue watch:{ user: { handler, deep: true, immediate: true } }
  useWatch(
    user, // 监听的源
    (newVal, oldVal) => { // 变化后的处理函数
      console.log('user变化:', '新值=', newVal, '旧值=', oldVal);
    },
    { deep: true, immediate: true } // 配置项
  );

  return <button onClick={() => setUser({ ...user, age: 19 })}>修改年龄</button>;
}

三、React vs Vue 核心对应关系

Vue 特性 React 实现方式 核心匹配点
computed useMemo + 依赖数组 基于依赖缓存,避免重复计算
watch 基础版 useEffect + 依赖数组 监听指定值,变化执行副作用
watch deep useDeepCompareEffect(自定义 Hook) 监听对象 / 数组深层变化
watch immediate useEffect 默认执行(或加标识位控制) 初始化时立即执行监听函数

总结

  1. React 实现computed的核心是useMemo,通过依赖数组控制缓存,完全匹配 Vue 计算属性的缓存特性;
  2. React 实现watch的基础是useEffect,深层监听需结合lodash.isEqual封装自定义 Hook,立即执行可通过useEffect默认行为实现;
  3. 封装useWatch Hook 可让写法更贴近 Vue 的watch,提升代码复用性,面试中提及这个点会加分。

这些写法覆盖了日常开发中 99% 的场景,且符合 React 官方最佳实践,可直接在项目中使用。

相关推荐
GISer_Jing44 分钟前
WebGL跨端兼容实战:移动端适配全攻略
前端·aigc·webgl
daols881 小时前
vue 甘特图 vxe-gantt 自定义任务条插槽模板的用法
vue.js·vxe-gantt
迦南giser1 小时前
前端性能——传输优化
前端
小白_ysf1 小时前
Vue 中常见的加密方法(对称、非对称、杂凑算法)
前端·vue.js·算法
人工智能训练7 小时前
【极速部署】Ubuntu24.04+CUDA13.0 玩转 VLLM 0.15.0:预编译 Wheel 包 GPU 版安装全攻略
运维·前端·人工智能·python·ai编程·cuda·vllm
会跑的葫芦怪8 小时前
若依Vue 项目多子路径配置
前端·javascript·vue.js
2601_949593658 小时前
基础入门 React Native 鸿蒙跨平台开发:模拟智能音响
react native·react.js·harmonyos
xiaoqi9229 小时前
React Native鸿蒙跨平台如何进行狗狗领养中心,实现基于唯一标识的事件透传方式是移动端列表开发的通用规范
javascript·react native·react.js·ecmascript·harmonyos
jin1233229 小时前
React Native鸿蒙跨平台剧本杀组队消息与快捷入口组件,包含消息列表展示、快捷入口管理、快捷操作触发和消息详情预览四大核心功能
javascript·react native·react.js·ecmascript·harmonyos
烬头882111 小时前
React Native鸿蒙跨平台实现二维码联系人APP(QRCodeContactApp)
javascript·react native·react.js·ecmascript·harmonyos