你想知道在 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 默认执行(或加标识位控制) | 初始化时立即执行监听函数 |
总结
- React 实现
computed的核心是useMemo,通过依赖数组控制缓存,完全匹配 Vue 计算属性的缓存特性; - React 实现
watch的基础是useEffect,深层监听需结合lodash.isEqual封装自定义 Hook,立即执行可通过useEffect默认行为实现; - 封装
useWatchHook 可让写法更贴近 Vue 的watch,提升代码复用性,面试中提及这个点会加分。
这些写法覆盖了日常开发中 99% 的场景,且符合 React 官方最佳实践,可直接在项目中使用。