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

