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 官方最佳实践,可直接在项目中使用。

相关推荐
杨超越luckly2 小时前
HTML应用指南:利用GET请求获取网易云热歌榜
前端·python·html·数据可视化·网易云热榜
多看书少吃饭2 小时前
OnlyOffice 编辑器的实现及使用
前端·vue.js·编辑器
编程之路从0到12 小时前
JSI入门指南
前端·c++·react native
开始学java2 小时前
别再写“一锅端”的 useEffect!聊聊 React 副作用的逻辑分离
前端
百度地图汽车版2 小时前
【智图译站】基于异步时空图卷积网络的不规则交通预测
前端·后端
qq_12498707532 小时前
基于Spring Boot的“味蕾探索”线上零食购物平台的设计与实现(源码+论文+部署+安装)
java·前端·数据库·spring boot·后端·小程序
用户65868180338402 小时前
Vue3 项目编码规范:基于Composable的清晰架构实践
vue.js
编程之路从0到12 小时前
React Native 之Android端 Bolts库
android·前端·react native
小酒星小杜2 小时前
在AI时代,技术人应该每天都要花两小时来构建一个自身的构建系统 - Build 篇
前端·vue.js·架构