前端框架React實(shí)現(xiàn)Vue的watch和computed詳解
一、前言
在前端框架生態(tài)中,Vue 的 watch 和 computed 是兩大核心且極具實(shí)用性的特性: computed 用于創(chuàng)建基于依賴的緩存計(jì)算屬性,避免無效重復(fù)計(jì)算; watch 用于監(jiān)聽數(shù)據(jù)變化并執(zhí)行副作用操作,支撐各類業(yè)務(wù)邏輯的響應(yīng)式觸發(fā)。在 React 開發(fā)中,我們同樣會遇到「需要緩存計(jì)算結(jié)果」和「監(jiān)聽數(shù)據(jù)變化執(zhí)行回調(diào)」的場景,同時(shí)這也是前端面試中的高頻考點(diǎn)。本文將詳細(xì)講解 React 生態(tài)中如何實(shí)現(xiàn) Vue 這兩大特性,涵蓋基礎(chǔ)寫法與進(jìn)階封裝,所有代碼均可直接落地項(xiàng)目。
二、實(shí)現(xiàn) Vue 的 computed(計(jì)算屬性)
在開始 React 實(shí)現(xiàn)之前,我們先明確 Vue computed 的核心特性:基于響應(yīng)式依賴進(jìn)行緩存。只有當(dāng) computed 依賴的數(shù)據(jù)源發(fā)生變化時(shí),才會重新執(zhí)行計(jì)算邏輯并更新結(jié)果;如果依賴未發(fā)生變化,直接返回緩存的上一次計(jì)算結(jié)果,從而避免無效的性能消耗,這也是 computed 與普通方法的核心區(qū)別。
方式一:基礎(chǔ)版(函數(shù)組件 + useMemo,推薦)
React 中實(shí)現(xiàn) computed 核心功能的最佳選擇是官方提供的 useMemo Hook,它完美匹配 Vue computed 的「緩存」核心特性,是項(xiàng)目開發(fā)中的推薦方案。
完整代碼示例
import { useState, useMemo } from 'react';
function ComputedDemo() {
// 1. 定義數(shù)據(jù)源(模擬 Vue 的 data 選項(xiàng))
const [num1, setNum1] = useState(10);
const [num2, setNum2] = useState(20);
const [unrelatedNum, setUnrelatedNum] = useState(0); // 與計(jì)算邏輯無關(guān)的變量
// 2. 使用 useMemo 實(shí)現(xiàn)計(jì)算屬性(模擬 Vue computed)
const sum = useMemo(() => {
console.log('useMemo 計(jì)算邏輯執(zhí)行了——僅依賴變化時(shí)觸發(fā)');
return num1 + num2;
}, [num1, num2]); // 依賴數(shù)組:僅 num1、num2 變化時(shí),重新計(jì)算 sum
return (
<div style={{ padding: '20px' }}>
<h3>React 實(shí)現(xiàn) Vue computed(useMemo 版)</h3>
<p>num1: {num1}</p>
<p>num2: {num2}</p>
<p>無關(guān)變量 unrelatedNum: {unrelatedNum}</p>
<p style={{ color: 'blue', fontWeight: 'bold' }}>計(jì)算屬性 sum(num1 + num2): {sum}</p>
{/* 3. 交互按鈕:修改相關(guān)依賴 */}
<button onClick={() => setNum1(prev => prev + 1)} style={{ marginRight: '10px' }}>
num1 + 1
</button>
<button onClick={() => setNum2(prev => prev + 1)} style={{ marginRight: '10px' }}>
num2 + 1
</button>
{/* 4. 交互按鈕:修改無關(guān)依賴 */}
<button onClick={() => setUnrelatedNum(prev => prev + 1)}>
無關(guān)變量 + 1
</button>
</div>
);
}
export default ComputedDemo;
核心解釋
useMemo兩個(gè)核心參數(shù)的作用:- 第一個(gè)參數(shù):計(jì)算邏輯函數(shù),返回值即為計(jì)算屬性的結(jié)果(對應(yīng) Vue
computed中的計(jì)算函數(shù)),該函數(shù)僅在依賴變化時(shí)執(zhí)行。 - 第二個(gè)參數(shù):依賴數(shù)組,存放當(dāng)前計(jì)算邏輯依賴的所有數(shù)據(jù)源(對應(yīng) Vue
computed自動(dòng)收集的響應(yīng)式依賴),只有數(shù)組中的變量發(fā)生變化時(shí),React 才會重新執(zhí)行第一個(gè)參數(shù)的計(jì)算函數(shù),更新計(jì)算結(jié)果。
- 第一個(gè)參數(shù):計(jì)算邏輯函數(shù),返回值即為計(jì)算屬性的結(jié)果(對應(yīng) Vue
緩存特性驗(yàn)證(對比「使用 useMemo」與「直接定義變量」):
- 上述示例中,點(diǎn)擊「num1 + 1」或「num2 + 1」(修改相關(guān)依賴),控制臺會打印日志,
sum也會同步更新,說明計(jì)算邏輯重新執(zhí)行。 - 點(diǎn)擊「無關(guān)變量 + 1」(修改不相關(guān)依賴),控制臺無日志輸出,
sum保持不變,說明useMemo生效,直接返回了緩存的計(jì)算結(jié)果。 - 若直接定義變量
const sum = num1 + num2;,每次組件重新渲染(無論是否修改相關(guān)依賴),都會重新執(zhí)行num1 + num2運(yùn)算,當(dāng)計(jì)算邏輯復(fù)雜時(shí)(如大量數(shù)據(jù)過濾、格式化),會造成不必要的性能損耗,這也是useMemo與普通變量定義的核心差異。
- 上述示例中,點(diǎn)擊「num1 + 1」或「num2 + 1」(修改相關(guān)依賴),控制臺會打印日志,
方式二:簡化版(僅簡單計(jì)算,無需緩存)
該方式適用于計(jì)算邏輯極簡單(如簡單的數(shù)值運(yùn)算、模板字符串拼接),且完全不關(guān)心重復(fù)計(jì)算帶來的性能損耗的場景,對應(yīng) Vue 中 computed 關(guān)閉緩存(極少使用)的場景。
簡短代碼示例
import { useState } from 'react';
function SimpleComputedDemo() {
// 定義數(shù)據(jù)源
const [firstName, setFirstName] = useState('Zhang');
const [lastName, setLastName] = useState('San');
// 簡化版:直接定義普通變量實(shí)現(xiàn)計(jì)算(無緩存)
const fullName = `${firstName} ${lastName}`; // 簡單模板字符串拼接
return (
<div style={{ padding: '20px' }}>
<h3>React 實(shí)現(xiàn) Vue computed(簡化版)</h3>
<p>firstName: {firstName}</p>
<p>lastName: {lastName}</p>
<p style={{ color: 'blue', fontWeight: 'bold' }}>計(jì)算結(jié)果 fullName: {fullName}</p>
<button onClick={() => setFirstName('Li')} style={{ marginRight: '10px' }}>
修改 firstName 為 Li
</button>
<button onClick={() => setLastName('Si')}>
修改 lastName 為 Si
</button>
</div>
);
}
export default SimpleComputedDemo;
說明
該方式無需引入任何 Hook,直接通過普通變量賦值實(shí)現(xiàn)計(jì)算需求,寫法簡潔。但缺點(diǎn)是無緩存,每次組件渲染(無論依賴是否變化)都會重新執(zhí)行計(jì)算邏輯,僅適用于計(jì)算成本極低的場景,不推薦在復(fù)雜計(jì)算中使用。
三、實(shí)現(xiàn) Vue 的 watch(監(jiān)聽數(shù)據(jù)變化)
Vue watch 的核心特性是:監(jiān)聽指定的響應(yīng)式數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時(shí),執(zhí)行預(yù)設(shè)的副作用函數(shù)(如發(fā)送接口請求、操作本地存儲、修改其他關(guān)聯(lián)數(shù)據(jù)等)。其核心應(yīng)用場景包括:基礎(chǔ)監(jiān)聽、深度監(jiān)聽(deep: true)、立即執(zhí)行(immediate: true),下面我們逐一在 React 中實(shí)現(xiàn)這些場景。
場景一:基礎(chǔ)監(jiān)聽(函數(shù)組件 + useEffect)
React 中處理副作用的核心 Hook 是 useEffect,通過控制其依賴數(shù)組,可以精準(zhǔn)匹配 Vue 基礎(chǔ)版 watch 的功能,這是實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽的基礎(chǔ)方案。
完整代碼示例
import { useState, useEffect } from 'react';
function BasicWatchDemo() {
// 1. 定義數(shù)據(jù)源(模擬 Vue data)
const [count, setCount] = useState(0); // 單個(gè)基礎(chǔ)類型值
const [user, setUser] = useState({ name: 'Zhang San', age: 25 }); // 對象類型
// 子場景 1:監(jiān)聽單個(gè)基礎(chǔ)類型值(對應(yīng) Vue 監(jiān)聽單個(gè)基礎(chǔ)數(shù)據(jù))
useEffect(() => {
console.log(`[基礎(chǔ)監(jiān)聽] count 發(fā)生變化,新值為:${count}`);
// 此處可執(zhí)行副作用邏輯:如接口請求、本地存儲等
}, [count]); // 依賴數(shù)組:僅 count 變化時(shí),執(zhí)行副作用函數(shù)
// 子場景 2:監(jiān)聽對象的某個(gè)具體屬性(對應(yīng) Vue 監(jiān)聽對象單個(gè)屬性)
useEffect(() => {
console.log(`[基礎(chǔ)監(jiān)聽] user.name 發(fā)生變化,新值為:${user.name}`);
// 此處可執(zhí)行副作用邏輯
}, [user.name]); // 依賴數(shù)組:僅 user.name 變化時(shí),執(zhí)行副作用函數(shù)
return (
<div style={{ padding: '20px' }}>
<h3>React 實(shí)現(xiàn) Vue watch(基礎(chǔ)監(jiān)聽版)</h3>
<p>count: {count}</p>
<p>user.name: {user.name}</p>
<p>user.age: {user.age}</p>
{/* 交互按鈕:修改 count */}
<button onClick={() => setCount(prev => prev + 1)} style={{ marginRight: '10px' }}>
count + 1
</button>
{/* 交互按鈕:修改 user.name */}
<button onClick={() => setUser(prev => ({ ...prev, name: 'Li Si' }))} style={{ marginRight: '10px' }}>
修改 user.name 為 Li Si
</button>
{/* 交互按鈕:修改 user.age(不觸發(fā) user.name 的監(jiān)聽) */}
<button onClick={() => setUser(prev => ({ ...prev, age: prev.age + 1 }))}>
user.age + 1
</button>
</div>
);
}
export default BasicWatchDemo;
核心解釋
- 依賴數(shù)組與監(jiān)聽目標(biāo)的關(guān)聯(lián):
useEffect的副作用函數(shù)執(zhí)行時(shí)機(jī)由依賴數(shù)組控制,只有當(dāng)依賴數(shù)組中的變量發(fā)生「淺層次變化」時(shí),副作用函數(shù)才會執(zhí)行,這與 Vue 基礎(chǔ)watch監(jiān)聽數(shù)據(jù)變化觸發(fā)回調(diào)的邏輯完全一致。 - 上述示例中:
- 點(diǎn)擊「count + 1」,僅觸發(fā) count 對應(yīng)的監(jiān)聽副作用。
- 點(diǎn)擊「修改 user.name」,僅觸發(fā) user.name 對應(yīng)的監(jiān)聽副作用。
- 點(diǎn)擊「user.age + 1」,由于未將 user.age 列入依賴數(shù)組,因此不會觸發(fā)任何副作用函數(shù),符合精準(zhǔn)監(jiān)聽的需求。
場景二:深度監(jiān)聽(模擬 Vue watch 的 deep: true)
在實(shí)際開發(fā)中,我們經(jīng)常需要監(jiān)聽復(fù)雜對象或數(shù)組的「深層屬性變化」(如 user.address.province、list[0].name),此時(shí)簡單的依賴數(shù)組無法實(shí)現(xiàn)需求,這就需要模擬 Vue watch 的 deep: true 配置,實(shí)現(xiàn)深度監(jiān)聽。
方案一:手動(dòng)監(jiān)聽所有深層屬性(適用于簡單對象)
對于屬性較少的簡單對象,可以將所有需要監(jiān)聽的深層屬性手動(dòng)列入 useEffect 的依賴數(shù)組,實(shí)現(xiàn)近似的深度監(jiān)聽效果。
代碼示例
import { useState, useEffect } from 'react';
function ManualDeepWatchDemo() {
// 定義深層對象數(shù)據(jù)源
const [user, setUser] = useState({
name: 'Zhang San',
address: {
province: 'Guangdong',
city: 'Shenzhen'
}
});
// 手動(dòng)監(jiān)聽深層屬性:user.name、user.address.province、user.address.city
useEffect(() => {
console.log(`[手動(dòng)深層監(jiān)聽] user 深層屬性發(fā)生變化,當(dāng)前 user:`, user);
}, [user.name, user.address.province, user.address.city]);
return (
<div style={{ padding: '20px' }}>
<h3>React 實(shí)現(xiàn) Vue watch(手動(dòng)深層監(jiān)聽版)</h3>
<p>user.name: {user.name}</p>
<p>user.address.province: {user.address.province}</p>
<p>user.address.city: {user.address.city}</p>
<button onClick={() => setUser(prev => ({
...prev,
address: { ...prev.address, city: 'Guangzhou' }
}))} style={{ marginRight: '10px' }}>
修改城市為 Guangzhou
</button>
<button onClick={() => setUser(prev => ({
...prev,
address: { ...prev.address, province: 'Jiangsu' }
}))}>
修改省份為 Jiangsu
</button>
</div>
);
}
export default ManualDeepWatchDemo;
局限性說明
該方案僅適用于屬性較少的簡單對象,當(dāng)對象結(jié)構(gòu)復(fù)雜(如多層嵌套、屬性數(shù)量眾多)時(shí),存在明顯弊端:
- 維護(hù)成本極高:需要手動(dòng)羅列所有深層屬性,遺漏任何一個(gè)都可能導(dǎo)致監(jiān)聽失效。
- 代碼冗余:依賴數(shù)組會變得異常冗長,降低代碼可讀性和可維護(hù)性。
- 無法應(yīng)對動(dòng)態(tài)屬性:對于數(shù)組、動(dòng)態(tài)添加的對象屬性,無法提前手動(dòng)羅列,監(jiān)聽效果受限。
因此,對于復(fù)雜對象或數(shù)組,推薦使用方案二。
方案二:自定義 Hook(useDeepCompareEffect,推薦)
該方案的核心思路是:通過 lodash.isEqual 實(shí)現(xiàn)深層數(shù)據(jù)對比,結(jié)合 useRef 緩存上一次的依賴數(shù)據(jù),僅當(dāng)深層對比發(fā)現(xiàn)數(shù)據(jù)變化時(shí),才執(zhí)行副作用函數(shù),完美模擬 Vue watch 的 deep: true。
步驟 1:安裝 lodash 依賴
npm install lodash # 或 yarn add lodash
步驟 2:自定義 Hook 封裝(useDeepCompareEffect)
import { useEffect, useRef } from 'react';
import isEqual from 'lodash/isEqual';
// 自定義深層對比 useEffect Hook(模擬 deep: true)
function useDeepCompareEffect(effect, deps) {
// 1. 使用 useRef 緩存上一次的依賴數(shù)據(jù)
const prevDepsRef = useRef();
// 2. 深層對比當(dāng)前依賴與上一次依賴是否一致
const depsChanged = !isEqual(deps, prevDepsRef.current);
// 3. 更新緩存的依賴數(shù)據(jù)(無論是否變化,都更新為當(dāng)前最新依賴)
if (depsChanged) {
prevDepsRef.current = deps;
}
// 4. 調(diào)用 useEffect,依賴數(shù)組為 [depsChanged](僅當(dāng)深層對比發(fā)現(xiàn)變化時(shí),執(zhí)行 effect)
useEffect(() => {
return effect();
}, [depsChanged]);
}
export default useDeepCompareEffect;
步驟 3:使用示例(監(jiān)聽復(fù)雜深層對象)
import { useState } from 'react';
import useDeepCompareEffect from './useDeepCompareEffect';
function DeepWatchDemo() {
// 定義復(fù)雜深層對象數(shù)據(jù)源
const [user, setUser] = useState({
name: 'Zhang San',
age: 25,
address: {
province: 'Guangdong',
city: 'Shenzhen',
detail: {
street: 'Nanshan Road',
number: '123'
}
},
hobbies: ['reading', 'running']
});
// 使用自定義 useDeepCompareEffect 實(shí)現(xiàn)深度監(jiān)聽
useDeepCompareEffect(() => {
console.log(`[深層監(jiān)聽] user 發(fā)生變化(包含深層屬性),當(dāng)前 user:`, user);
}, [user]); // 依賴數(shù)組直接傳入整個(gè) user,無需手動(dòng)羅列深層屬性
return (
<div style={{ padding: '20px' }}>
<h3>React 實(shí)現(xiàn) Vue watch(深層監(jiān)聽版)</h3>
<p>user.name: {user.name}</p>
<p>user.address.detail.street: {user.address.detail.street}</p>
<p>user.hobbies: {user.hobbies.join(', ')}</p>
<button onClick={() => setUser(prev => ({
...prev,
address: {
...prev.address,
detail: { ...prev.address.detail, street: 'Futian Road' }
}
}))} style={{ marginRight: '10px' }}>
修改街道為 Futian Road
</button>
<button onClick={() => setUser(prev => ({
...prev,
hobbies: [...prev.hobbies, 'swimming']
}))}>
添加愛好 swimming
</button>
</div>
);
}
export default DeepWatchDemo;
說明
該方案完美解決了復(fù)雜對象的深度監(jiān)聽問題,無需手動(dòng)羅列深層屬性,維護(hù)成本低,是項(xiàng)目開發(fā)中處理深度監(jiān)聽的推薦方案,其核心邏輯是通過 lodash.isEqual 忽略引用地址,僅對比數(shù)據(jù)的深層內(nèi)容是否一致。
場景三:立即執(zhí)行(模擬 Vue watch 的 immediate: true)
Vue watch 的 immediate: true 配置用于實(shí)現(xiàn)「監(jiān)聽函數(shù)在首次渲染時(shí)立即執(zhí)行一次,后續(xù)依賴變化時(shí)再正常執(zhí)行」。而 React 中 useEffect 的默認(rèn)行為就是:首次組件渲染時(shí)執(zhí)行一次副作用函數(shù),之后僅當(dāng)依賴變化時(shí)再次執(zhí)行,這與 immediate: true 的邏輯完全匹配,實(shí)現(xiàn)起來非常簡潔。
代碼示例 1:默認(rèn)立即執(zhí)行(簡潔寫法)
import { useState, useEffect } from 'react';
function ImmediateWatchDemo1() {
const [count, setCount] = useState(0);
// useEffect 默認(rèn)行為:首次渲染 + count 變化時(shí)執(zhí)行(對應(yīng) Vue immediate: true)
useEffect(() => {
console.log(`[立即執(zhí)行] count 監(jiān)聽觸發(fā),當(dāng)前值:${count}`);
// 副作用邏輯:如初始化請求、首次渲染后的數(shù)據(jù)處理等
}, [count]);
return (
<div style={{ padding: '20px' }}>
<h3>React 實(shí)現(xiàn) Vue watch(立即執(zhí)行版)</h3>
<p>count: {count}</p>
<button onClick={() => setCount(prev => prev + 1)}>
count + 1
</button>
</div>
);
}
export default ImmediateWatchDemo1;
代碼示例 2:取消「立即執(zhí)行」(僅數(shù)據(jù)變化時(shí)執(zhí)行)
有時(shí)我們需要模擬 Vue watch 的默認(rèn)行為(immediate: false,僅數(shù)據(jù)變化時(shí)執(zhí)行,首次渲染不執(zhí)行),此時(shí)可以通過 useRef 定義一個(gè)標(biāo)識位,控制首次渲染時(shí)不執(zhí)行副作用邏輯。
import { useState, useEffect, useRef } from 'react';
function ImmediateWatchDemo2() {
const [count, setCount] = useState(0);
const isFirstRender = useRef(true); // 標(biāo)識位:標(biāo)記是否為首次渲染
useEffect(() => {
// 首次渲染時(shí),直接返回,不執(zhí)行副作用邏輯
if (isFirstRender.current) {
isFirstRender.current = false; // 重置標(biāo)識位,后續(xù)渲染不再生效
return;
}
// 非首次渲染(僅 count 變化時(shí)),執(zhí)行副作用邏輯
console.log(`[取消立即執(zhí)行] count 發(fā)生變化,當(dāng)前值:${count}`);
}, [count]);
return (
<div style={{ padding: '20px' }}>
<h3>React 實(shí)現(xiàn) Vue watch(取消立即執(zhí)行版)</h3>
<p>count: {count}</p>
<button onClick={() => setCount(prev => prev + 1)}>
count + 1
</button>
</div>
);
}
export default ImmediateWatchDemo2;
進(jìn)階:封裝 Vue 風(fēng)格的 useWatch 自定義 Hook(提升復(fù)用性)
上述方案已經(jīng)實(shí)現(xiàn)了 Vue watch 的核心功能,但在多個(gè)組件中使用時(shí)會存在重復(fù)代碼。我們可以封裝一個(gè)貼近 Vue 寫法的 useWatch 自定義 Hook,支持 immediate 和 deep 兩個(gè)配置項(xiàng),提升代碼復(fù)用性。
完整封裝代碼
import { useEffect, useRef } from 'react';
import isEqual from 'lodash/isEqual';
// 自定義 Vue 風(fēng)格 useWatch Hook
function useWatch(watchSource, callback, options = { immediate: false, deep: false }) {
const { immediate, deep } = options;
const prevSourceRef = useRef(); // 緩存上一次的監(jiān)聽源數(shù)據(jù)
const isFirstRender = useRef(true); // 標(biāo)記是否為首次渲染
// 處理監(jiān)聽邏輯
useEffect(() => {
// 1. 處理首次渲染立即執(zhí)行
if (isFirstRender.current) {
isFirstRender.current = false;
if (immediate) {
callback(watchSource, undefined); // 首次執(zhí)行:新值為當(dāng)前監(jiān)聽源,舊值為 undefined
return;
}
}
// 2. 處理深度對比 vs 淺對比
const sourceChanged = deep
? !isEqual(watchSource, prevSourceRef.current)
: watchSource !== prevSourceRef.current;
// 3. 監(jiān)聽源變化時(shí),執(zhí)行回調(diào)函數(shù)
if (sourceChanged) {
callback(watchSource, prevSourceRef.current); // 傳遞新值和舊值
}
// 4. 更新緩存的監(jiān)聽源數(shù)據(jù)
prevSourceRef.current = deep ? JSON.parse(JSON.stringify(watchSource)) : watchSource;
}, [watchSource, callback, immediate, deep]);
}
export default useWatch;
使用示例
import { useState } from 'react';
import useWatch from './useWatch';
function VueStyleWatchDemo() {
// 定義監(jiān)聽源
const [user, setUser] = useState({
name: 'Zhang San',
age: 25,
address: {
city: 'Shenzhen'
}
});
// 使用封裝的 useWatch(支持 deep: true + immediate: true)
useWatch(
user, // 監(jiān)聽源(對應(yīng) Vue watch 的監(jiān)聽目標(biāo))
(newValue, oldValue) => { // 回調(diào)函數(shù)(對應(yīng) Vue watch 的處理函數(shù))
console.log('[Vue 風(fēng)格 useWatch] 監(jiān)聽觸發(fā)');
console.log('新值:', newValue);
console.log('舊值:', oldValue);
},
{ immediate: true, deep: true } // 配置項(xiàng)(對應(yīng) Vue watch 的 immediate 和 deep)
);
return (
<div style={{ padding: '20px' }}>
<h3>React 封裝 Vue 風(fēng)格 useWatch</h3>
<p>user.name: {user.name}</p>
<p>user.address.city: {user.address.city}</p>
<button onClick={() => setUser(prev => ({
...prev,
address: { ...prev.address, city: 'Guangzhou' }
}))}>
修改城市為 Guangzhou
</button>
</div>
);
}
export default VueStyleWatchDemo;
說明
該自定義 useWatch Hook 完全貼近 Vue watch 的寫法,支持新舊值傳遞、立即執(zhí)行和深度監(jiān)聽配置,可直接在項(xiàng)目中復(fù)用,同時(shí)也是前端面試中的加分項(xiàng),體現(xiàn)了開發(fā)者的 Hook 封裝能力和對 React 副作用的理解。
四、React vs Vue 核心對應(yīng)關(guān)系(匯總表)
| Vue 特性 | React 實(shí)現(xiàn)方式 | 核心匹配點(diǎn) |
|---|---|---|
| computed(帶緩存) | 函數(shù)組件 + useMemo Hook | 基于依賴緩存,依賴不變時(shí)不重復(fù)計(jì)算,優(yōu)化性能 |
| computed(無緩存) | 普通變量直接賦值(簡單計(jì)算) | 實(shí)現(xiàn)基礎(chǔ)計(jì)算需求,無緩存,寫法簡潔 |
| watch(基礎(chǔ)監(jiān)聽) | 函數(shù)組件 + useEffect Hook | 依賴變化時(shí)執(zhí)行副作用,精準(zhǔn)監(jiān)聽單個(gè)/多個(gè)基礎(chǔ)數(shù)據(jù) |
| watch(deep: true) | 1. 手動(dòng)羅列深層屬性(簡單對象) 2. 自定義 useDeepCompareEffect(復(fù)雜對象,依賴 lodash.isEqual) | 忽略引用地址,監(jiān)聽對象/數(shù)組的深層內(nèi)容變化 |
| watch(immediate: true) | 函數(shù)組件 + useEffect Hook(默認(rèn)行為) | 首次渲染 + 依賴變化時(shí)執(zhí)行副作用 |
| watch(immediate: false) | 函數(shù)組件 + useEffect + useRef 標(biāo)識位 | 僅依賴變化時(shí)執(zhí)行副作用,首次渲染不執(zhí)行 |
| 完整 Vue 風(fēng)格 watch | 自定義 useWatch Hook(封裝 useEffect + 深層對比) | 支持 deep、immediate 配置,貼近 Vue 寫法,提升復(fù)用性 |
五、總結(jié)與落地說明
1. 核心要點(diǎn)回顧
- React 實(shí)現(xiàn) Vue
computed的核心是useMemoHook,其緩存特性與computed完全匹配,是復(fù)雜計(jì)算場景的首選方案;簡單計(jì)算可直接使用普通變量賦值,兼顧簡潔性。 - React 實(shí)現(xiàn) Vue
watch的基礎(chǔ)是useEffectHook,通過依賴數(shù)組控制副作用執(zhí)行時(shí)機(jī),實(shí)現(xiàn)基礎(chǔ)監(jiān)聽;深層監(jiān)聽需結(jié)合lodash.isEqual進(jìn)行深層數(shù)據(jù)對比,封裝自定義 Hook 提升復(fù)用性;立即執(zhí)行與取消立即執(zhí)行的核心是通過useRef標(biāo)識位控制首次渲染的邏輯。 - 所有實(shí)現(xiàn)方案均符合 React 官方最佳實(shí)踐,無額外第三方框架依賴(僅深層監(jiān)聽需引入 lodash),穩(wěn)定性有保障。
2. 實(shí)用價(jià)值強(qiáng)調(diào)
本文覆蓋了日常 React 開發(fā)中 99% 的「計(jì)算屬性」和「數(shù)據(jù)監(jiān)聽」場景,從基礎(chǔ)寫法到進(jìn)階封裝,代碼均可直接復(fù)制落地。其中,自定義 useDeepCompareEffect 和 useWatch Hook 不僅能減少項(xiàng)目中的重復(fù)代碼,還能在前端面試中展現(xiàn)個(gè)人的技術(shù)深度和工程化思維,是加分的亮點(diǎn)。
3. 落地補(bǔ)充提示
- 項(xiàng)目中可將
useDeepCompareEffect和useWatch抽離為公共 Hook(如放在src/hooks/目錄下),統(tǒng)一管理和維護(hù),方便所有組件引入使用。 - 若項(xiàng)目中已引入 lodash,可直接使用
lodash.isEqual;若不想引入完整 lodash,可單獨(dú)安裝lodash.isEqual(npm install lodash.isEqual),減小打包體積。 - 對于極致性能優(yōu)化的場景,可結(jié)合
useCallback緩存回調(diào)函數(shù),與useMemo、useEffect配合使用,進(jìn)一步減少不必要的組件重渲染。 - 隨著 React 生態(tài)的發(fā)展,也可選擇成熟的第三方 Hook 庫(如
react-use),其中已封裝了完善的深層監(jiān)聽、數(shù)據(jù)監(jiān)聽 Hook,快速提升開發(fā)效率。
通過本文的學(xué)習(xí),相信你已經(jīng)能夠在 React 項(xiàng)目中靈活實(shí)現(xiàn) Vue watch 和 computed 的核心功能,從容應(yīng)對各類響應(yīng)式場景的開發(fā)需求。
到此這篇關(guān)于前端框架React實(shí)現(xiàn)Vue的watch和computed詳解的文章就介紹到這了,更多相關(guān)React實(shí)現(xiàn)Vue的watch和computed內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
五步教你用Nginx部署Vue項(xiàng)目及解決動(dòng)態(tài)路由刷新404問題
nginx 是一個(gè)代理的服務(wù)器,下面這篇文章主要給大家介紹了關(guān)于如何通過五步教你用Nginx部署Vue項(xiàng)目及解決動(dòng)態(tài)路由刷新404問題的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12
vue實(shí)現(xiàn)未登錄訪問其他頁面自動(dòng)跳轉(zhuǎn)登錄頁功能(實(shí)現(xiàn)步驟)
這篇文章主要介紹了vue實(shí)現(xiàn)未登錄下訪問其他頁面自動(dòng)跳轉(zhuǎn)登錄頁,本文分步驟給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
Vue動(dòng)態(tài)引入JS文件實(shí)現(xiàn)方式
這篇文章主要介紹了Vue動(dòng)態(tài)引入JS文件實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2026-05-05
vue.js的雙向數(shù)據(jù)綁定Object.defineProperty方法的神奇之處
今天小編就為大家分享一篇關(guān)于vue.js的雙向數(shù)據(jù)綁定Object.defineProperty方法的神奇之處,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-01-01
詳解如何在Vue2中實(shí)現(xiàn)組件props雙向綁定
在Vue2中組件的props的數(shù)據(jù)流動(dòng)改為了只能單向流動(dòng),如何在Vue2中實(shí)現(xiàn)組件props雙向綁定 ,一起來跟小編看看。2017-03-03
Vue中登錄驗(yàn)證成功后保存token,并每次請求攜帶并驗(yàn)證token操作
這篇文章主要介紹了Vue中登錄驗(yàn)證成功后保存token,并每次請求攜帶并驗(yàn)證token操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09

