速通React之Hooks的實(shí)現(xiàn)
本文系統(tǒng)梳理 React 中 8 個(gè)最常用的核心 Hooks:useState、useReducer、useContext、useEffect、useRef、useImperativeHandle、useMemo、useCallback。從底層運(yùn)行機(jī)制到高階實(shí)戰(zhàn)用法,配合完整可運(yùn)行的代碼塊,助你徹底掌握函數(shù)式組件的開(kāi)發(fā)精髓。
前言
React 16.8 正式引入 Hooks,使得函數(shù)組件擁有了類組件的全部能力(狀態(tài)、生命周期、上下文等),同時(shí)摒棄了類組件中 this 指向混亂、邏輯復(fù)用困難(HOC / Render Props)等問(wèn)題。Hooks 的本質(zhì)是將組件狀態(tài)邏輯與視圖邏輯進(jìn)行解耦,其底層依賴 React 內(nèi)部的 Fiber 架構(gòu),通過(guò)鏈表(memoizedState)來(lái)按順序存儲(chǔ)各個(gè) Hook 的狀態(tài)值。
核心鐵律(必須遵守) :
- 只在 React 函數(shù)組件或自定義 Hook 中調(diào)用 Hooks。
- 只在頂層調(diào)用 Hooks(不在循環(huán)、條件或嵌套函數(shù)中),以保證 Hooks 的調(diào)用順序在每次渲染中一致。
1. useState —— 響應(yīng)式狀態(tài)的基石
原理
useState 是 React 內(nèi)部狀態(tài)管理的最小單元。在 Fiber 節(jié)點(diǎn)上,狀態(tài)以單向鏈表的形式存儲(chǔ)。每次渲染時(shí),React 根據(jù) Hook 的調(diào)用順序依次從鏈表上讀取對(duì)應(yīng)的狀態(tài)值。setState 會(huì)觸發(fā)組件重新渲染,并生成新的 Fiber 樹(shù)。
作用
為函數(shù)組件添加內(nèi)部狀態(tài),狀態(tài)改變時(shí) UI 自動(dòng)更新。
語(yǔ)法
const [state, setState] = useState(initialState);
- 支持函數(shù)式更新:
setState(prev => prev + 1)可避免因閉包導(dǎo)致的狀態(tài)過(guò)期問(wèn)題。
示例
import { useState } from 'react';
function UserForm() {
const [username, setUsername] = useState('');
const [age, setAge] = useState(18);
const handleSubmit = () => {
alert(`用戶名:${username},年齡:${age}`);
};
return (
<div>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="請(qǐng)輸入用戶名"
/>
<input
type="number"
value={age}
onChange={(e) => setAge(Number(e.target.value))}
/>
<button onClick={handleSubmit}>提交</button>
</div>
);
}
2. useReducer —— 復(fù)雜狀態(tài)邏輯的治理專家
原理
與 useState 共享底層狀態(tài)存儲(chǔ)機(jī)制,但將狀態(tài)更新邏輯集中到 reducer 純函數(shù)中。通過(guò) dispatch 派發(fā) action,reducer 根據(jù) action.type 計(jì)算出新?tīng)顟B(tài)。這種模式源自 Redux,讓狀態(tài)變化可預(yù)測(cè)、可追蹤。
作用
- 當(dāng)狀態(tài)更新邏輯包含多個(gè)子值或復(fù)雜轉(zhuǎn)換(如購(gòu)物車、表單校驗(yàn))時(shí),比
useState更具可讀性。 - 適合狀態(tài)更新依賴先前狀態(tài),且操作類型多樣(增、刪、改、重置)的場(chǎng)景。
語(yǔ)法
const [state, dispatch] = useReducer(reducer, initialArg, init);
示例:購(gòu)物車數(shù)量管理
import { useReducer } from 'react';
// 1. 定義 reducer
function cartReducer(state, action) {
switch (action.type) {
case 'ADD':
return { ...state, count: state.count + 1 };
case 'SUBTRACT':
return { ...state, count: Math.max(0, state.count - 1) };
case 'RESET':
return { ...state, count: 0 };
default:
return state;
}
}
function ShoppingCart() {
const [state, dispatch] = useReducer(cartReducer, { count: 0 });
return (
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
<button onClick={() => dispatch({ type: 'SUBTRACT' })}>-</button>
<span>數(shù)量:{state.count}</span>
<button onClick={() => dispatch({ type: 'ADD' })}>+</button>
<button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
</div>
);
}
3. useContext —— 跨層級(jí)數(shù)據(jù)傳遞的直通車
原理
useContext 基于 React 的 Context API。當(dāng)上層組件通過(guò) <Provider value={...}> 提供數(shù)據(jù)時(shí),React 會(huì)在 Fiber 節(jié)點(diǎn)上維護(hù)一個(gè) context 鏈。下層調(diào)用 useContext 會(huì)向上查找最近的 Provider,并訂閱其值。當(dāng) Provider 的 value 變化時(shí),所有訂閱該 Context 的組件會(huì)觸發(fā)更新。
作用
徹底解決 Props Drilling(屬性鉆?。﹩?wèn)題,讓數(shù)據(jù)在組件樹(shù)中無(wú)障礙傳遞,常用于主題、語(yǔ)言環(huán)境、用戶鑒權(quán)信息等全局?jǐn)?shù)據(jù)。
語(yǔ)法
const MyContext = React.createContext(defaultValue); const value = useContext(MyContext);
示例:全局主題切換
import { createContext, useContext, useState } from 'react';
// 1. 創(chuàng)建 Context
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
// 2. 提供者組件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((t) => (t === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. 深層子組件消費(fèi)
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
const isDark = theme === 'dark';
return (
<button
onClick={toggleTheme}
style={{
background: isDark ? '#333' : '#fff',
color: isDark ? '#fff' : '#000',
padding: '8px 16px',
border: '1px solid #ccc',
cursor: 'pointer',
}}
>
當(dāng)前主題:{theme},點(diǎn)擊切換
</button>
);
}
function Toolbar() {
return <ThemedButton />; // 中間組件無(wú)需透?jìng)魅魏?props
}
// 4. 使用
export default function App() {
return (
<ThemeProvider>
<Toolbar />
</ThemeProvider>
);
}
4. useEffect —— 副作用與生命周期的統(tǒng)一管理
原理
useEffect 的調(diào)度基于 Fiber 的 Effect List。在瀏覽器完成布局與繪制之后,React 會(huì)異步執(zhí)行 Effect 函數(shù)(默認(rèn))。通過(guò)依賴數(shù)組,React 使用 Object.is 比較前后依賴項(xiàng),決定是否跳過(guò)執(zhí)行。清理函數(shù)會(huì)在組件卸載或下一次 Effect 執(zhí)行之前調(diào)用,用于清除定時(shí)器、取消訂閱等。
作用
- 替代類組件生命周期:
componentDidMount(空依賴)、componentDidUpdate(指定依賴)、componentWillUnmount(清理函數(shù))。 - 執(zhí)行數(shù)據(jù)獲取、手動(dòng) DOM 操作、訂閱事件等副作用。
語(yǔ)法
useEffect(() => {
// 副作用邏輯
return () => {
// 清理邏輯(可選)
};
}, [dependencies]);
示例:獲取用戶數(shù)據(jù)并設(shè)置定時(shí)器
import { useState, useEffect } from 'react';
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true; // 防止組件卸載后依然 setState
// 模擬異步請(qǐng)求
const fetchData = async () => {
try {
const response = await new Promise((resolve) =>
setTimeout(() => resolve({ name: '王五', age: 30 }), 2000)
);
if (isMounted) {
setUser(response);
setLoading(false);
}
} catch (error) {
if (isMounted) setLoading(false);
}
};
fetchData();
// 清理函數(shù):組件卸載時(shí)取消掛載標(biāo)記
return () => {
isMounted = false;
};
}, []); // 僅在掛載時(shí)執(zhí)行一次
if (loading) return <p>加載中...</p>;
return (
<div>
<h2>用戶信息</h2>
<p>姓名:{user.name}</p>
<p>年齡:{user.age}</p>
</div>
);
}
5. useRef —— 存儲(chǔ)可變數(shù)據(jù)與 DOM 引用
原理
useRef 返回一個(gè) { current: initialValue } 對(duì)象。該對(duì)象在組件的整個(gè)生命周期內(nèi)內(nèi)存地址恒定(類似類組件的實(shí)例屬性)。修改 current 不會(huì)觸發(fā)組件重新渲染,這使得它非常適合存儲(chǔ)“不影響視圖”的可變數(shù)據(jù)。
作用
- 獲取 DOM 元素的引用(如聚焦、測(cè)量尺寸)。
- 存儲(chǔ)上一次渲染的值、定時(shí)器 ID、WebSocket 實(shí)例等跨渲染周期數(shù)據(jù)。
語(yǔ)法
const refContainer = useRef(initialValue); // 使用:refContainer.current
示例一:自動(dòng)聚焦輸入框
import { useRef, useEffect } from 'react';
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="頁(yè)面加載后自動(dòng)聚焦" />;
}
示例二:保存計(jì)時(shí)器 ID 并清除
import { useRef, useState } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
const timerIdRef = useRef(null);
const start = () => {
if (timerIdRef.current) return; // 防止重復(fù)啟動(dòng)
timerIdRef.current = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
};
const stop = () => {
clearInterval(timerIdRef.current);
timerIdRef.current = null;
};
return (
<div>
<p>計(jì)時(shí):{seconds} 秒</p>
<button onClick={start}>開(kāi)始</button>
<button onClick={stop}>停止</button>
</div>
);
}
6. useImperativeHandle —— 封裝子組件的命令式接口
原理
useImperativeHandle 必須與 forwardRef 配合。父組件傳遞的 ref 對(duì)象會(huì)被子組件接收,useImperativeHandle 允許子組件自定義掛載到 ref.current 上的內(nèi)容(通常是一個(gè)方法對(duì)象)。這通過(guò)劫持 ref 的映射過(guò)程實(shí)現(xiàn),防止父組件直接操作子組件的 DOM 或內(nèi)部狀態(tài),增強(qiáng)封裝性。
作用
- 暴露特定方法給父組件(如
focus、reset、validate)。 - 控制父組件對(duì)子組件的訪問(wèn)權(quán)限,避免破壞子組件的內(nèi)部邏輯。
語(yǔ)法
useImperativeHandle(ref, () => ({
// 返回暴露的方法
}), [dependencies]);
示例:父組件調(diào)用子組件表單重置與聚焦
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
// 子組件
const CustomInput = forwardRef((props, ref) => {
const [value, setValue] = useState('');
const inputRef = useRef(null);
// 暴露方法給父組件
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
reset: () => {
setValue('');
inputRef.current.focus();
},
getValue: () => value,
}));
return (
<input
ref={inputRef}
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="子組件輸入框"
/>
);
});
// 父組件
function ParentForm() {
const childRef = useRef(null);
const handleReset = () => {
childRef.current.reset();
};
const handleGetValue = () => {
alert(childRef.current.getValue());
};
return (
<div>
<CustomInput ref={childRef} />
<button onClick={handleReset}>重置并聚焦</button>
<button onClick={handleGetValue}>獲取子組件值</button>
</div>
);
}
7. useMemo —— 記憶化計(jì)算結(jié)果,提升渲染性能
原理
useMemo 利用閉包存儲(chǔ)上一次的返回值。在渲染階段,React 會(huì)對(duì)比依賴項(xiàng)數(shù)組,若依賴未變,直接返回緩存的舊值,跳過(guò)函數(shù)執(zhí)行。其緩存掛載在 Fiber 節(jié)點(diǎn)的 memoizedState 上,屬于渲染期間的計(jì)算優(yōu)化。
作用
- 緩存高開(kāi)銷的計(jì)算(如大數(shù)據(jù)過(guò)濾、復(fù)雜數(shù)學(xué)運(yùn)算)。
- 保持引用類型的穩(wěn)定性(如對(duì)象、數(shù)組),避免因每次渲染重新創(chuàng)建而導(dǎo)致子組件(
React.memo)無(wú)效重繪。
語(yǔ)法
const memoizedValue = useMemo(() => compute(a, b), [a, b]);
示例:大數(shù)據(jù)過(guò)濾與輸入框絲滑交互
import { useState, useMemo } from 'react';
function FilterList() {
const [keyword, setKeyword] = useState('');
const [count, setCount] = useState(0);
// 模擬一個(gè)龐大的靜態(tài)數(shù)據(jù)列表
const allItems = useMemo(() => {
const items = [];
for (let i = 0; i < 20000; i++) {
items.push(`商品-${i}`);
}
return items;
}, []); // 只在首次渲染生成一次
// 根據(jù)關(guān)鍵詞過(guò)濾(僅當(dāng) keyword 或 allItems 變化時(shí)重新計(jì)算)
const filteredItems = useMemo(() => {
console.log('執(zhí)行過(guò)濾計(jì)算...');
if (!keyword) return allItems;
return allItems.filter((item) => item.includes(keyword));
}, [keyword, allItems]);
return (
<div>
<input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="輸入關(guān)鍵詞過(guò)濾(體驗(yàn)絲滑輸入)"
/>
<button onClick={() => setCount((c) => c + 1)}>重渲染計(jì)數(shù)器:{count}</button>
<ul style={{ height: '300px', overflow: 'auto', border: '1px solid #ccc' }}>
{filteredItems.slice(0, 100).map((item) => (
<li key={item}>{item}</li>
))}
{filteredItems.length > 100 && <li>...僅顯示前100條</li>}
</ul>
</div>
);
}
點(diǎn)擊計(jì)數(shù)器改變 count 時(shí),組件重渲染,但 filteredItems 因依賴未變直接命中緩存,列表不會(huì)重新過(guò)濾,輸入框交互依然絲滑。
8. useCallback —— 記憶化函數(shù)引用,優(yōu)化子組件渲染
原理
useCallback 本質(zhì)是 useMemo(() => fn, deps) 的語(yǔ)法糖,返回的是一個(gè)記憶化的函數(shù)。當(dāng)依賴項(xiàng)不變時(shí),函數(shù)的引用地址保持不變。
作用
- 當(dāng)父組件重渲染時(shí),傳遞給 React.memo 子組件的回調(diào)函數(shù)引用不變,子組件得以跳過(guò)渲染。
- 作為 useEffect 的依賴項(xiàng)時(shí),避免因函數(shù)重新創(chuàng)建而導(dǎo)致 Effect 頻繁執(zhí)行。
語(yǔ)法
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
示例:配合 React.memo 避免子組件無(wú)效重繪
import { useState, useCallback, memo } from 'react';
// 子組件用 memo 包裹,進(jìn)行淺層 props 比較
const Child = memo(({ onAdd, label }) => {
console.log(`Child ${label} 渲染了`);
return <button onClick={onAdd}>增加 {label}</button>;
});
function Parent() {
const [countA, setCountA] = useState(0);
const [countB, setCountB] = useState(0);
const [text, setText] = useState('');
// 使用 useCallback 緩存,依賴為空,函數(shù)永遠(yuǎn)不變
const handleAddA = useCallback(() => {
setCountA((prev) => prev + 1);
}, []);
// 此處故意不使用 useCallback,每次渲染都會(huì)創(chuàng)建新函數(shù)
const handleAddB = () => {
setCountB((prev) => prev + 1);
};
return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="在此輸入,觀察子組件渲染情況"
/>
<p>Count A: {countA}</p>
<p>Count B: {countB}</p>
<Child onAdd={handleAddA} label="A (優(yōu)化)" />
<Child onAdd={handleAddB} label="B (未優(yōu)化)" />
</div>
);
}
當(dāng)在輸入框中打字時(shí),父組件重渲染。
Child A因handleAddA引用不變,不會(huì)重新渲染;Child B因每次接收新的handleAddB函數(shù),導(dǎo)致 props 變化,觸發(fā)不必要的重渲染。
擴(kuò)展補(bǔ)充:React.memo(非 Hook,但密切配合)
React.memo 是一個(gè)高階組件(HOC),用于緩存組件。它對(duì) props 進(jìn)行淺層比較,若 props 未變則跳過(guò)本次渲染。常與 useCallback 和 useMemo 組成性能優(yōu)化的“鐵三角”。
const MyComponent = memo(function MyComponent(props) {
// 僅當(dāng) props 改變時(shí)才重新渲染
});
完整總結(jié)對(duì)照表
| Hook 名稱 | 核心職責(zé) | 典型應(yīng)用場(chǎng)景 |
|---|---|---|
| useState | 管理簡(jiǎn)單局部響應(yīng)式狀態(tài) | 表單輸入、開(kāi)關(guān)、計(jì)數(shù)器 |
| useReducer | 管理含有復(fù)雜邏輯的局部狀態(tài) | 購(gòu)物車、具有多種操作類型的對(duì)象狀態(tài) |
| useContext | 跨組件樹(shù)共享數(shù)據(jù),避免 Props 透?jìng)?/td> | 主題、語(yǔ)言、用戶認(rèn)證信息 |
| useEffect | 執(zhí)行副作用與生命周期管理 | 數(shù)據(jù)請(qǐng)求、DOM 操作、訂閱與清理 |
| useRef | 存儲(chǔ)可變數(shù)據(jù)(不觸發(fā)渲染)及 DOM 引用 | 獲取 DOM 節(jié)點(diǎn)、保存定時(shí)器 ID、記錄上一次值 |
| useImperativeHandle | 自定義暴露給父組件的實(shí)例方法 | 封裝表單組件、暴露 focus / reset 方法 |
| useMemo | 記憶化計(jì)算結(jié)果,緩存對(duì)象/數(shù)組引用 | 大數(shù)據(jù)計(jì)算、穩(wěn)定子組件 props |
| useCallback | 記憶化函數(shù)引用,配合 memo 優(yōu)化 | 傳遞給子組件的回調(diào)函數(shù) |
進(jìn)階提示(并非全部,但值得了解)
除了上述 8 個(gè)核心 Hook,React 還提供了:
- useLayoutEffect:與 useEffect 類似,但在 DOM 更新后、瀏覽器繪制前同步執(zhí)行,適合處理 DOM 尺寸測(cè)量等需要避免閃爍的操作。
- useDebugValue:在 React DevTools 中為自定義 Hook 顯示標(biāo)簽,方便調(diào)試。
- useId:生成穩(wěn)定且唯一的 ID,用于 SSR 場(chǎng)景下的無(wú)障礙屬性(如 aria-describedby)。
- useTransition / useDeferredValue:用于并發(fā)模式下的 UI 更新降級(jí)與渲染優(yōu)化。
到此這篇關(guān)于速通React之Hooks的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)React Hooks內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React報(bào)錯(cuò)之Parameter event implicitly has a
這篇文章主要為大家介紹了React報(bào)錯(cuò)之Parameter event implicitly has an any type,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
詳解基于React.js和Node.js的SSR實(shí)現(xiàn)方案
這篇文章主要介紹了詳解基于React.js和Node.js的SSR實(shí)現(xiàn)方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
react 國(guó)際化的實(shí)現(xiàn)代碼示例
這篇文章主要介紹了react 國(guó)際化的實(shí)現(xiàn)代碼示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
React實(shí)現(xiàn)錨點(diǎn)跳轉(zhuǎn)組件附帶吸頂效果的示例代碼
這篇文章主要為大家詳細(xì)介紹了React如何實(shí)現(xiàn)移動(dòng)端錨點(diǎn)跳轉(zhuǎn)組件附帶吸頂效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-01-01
在?React?Native?中給第三方庫(kù)打補(bǔ)丁的過(guò)程解析
這篇文章主要介紹了在?React?Native?中給第三方庫(kù)打補(bǔ)丁的過(guò)程解析,有時(shí)使用了某個(gè)React Native 第三方庫(kù),可是它有些問(wèn)題,我們不得不修改它的源碼,本文介紹如何修改源碼又不會(huì)意外丟失修改結(jié)果的方法,需要的朋友可以參考下2022-08-08
React?Hooks使用startTransition與useTransition教程示例
這篇文章主要為大家介紹了React?Hooks使用startTransition與useTransition教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Redis數(shù)據(jù)結(jié)構(gòu)面試高頻問(wèn)題解析
這篇文章主要為大家介紹了Redis數(shù)據(jù)結(jié)構(gòu)高頻面試問(wèn)題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06

