【第28期】一文學(xué)會(huì)使用React Hooks
概述
React Hooks是React 16.8版本引入的一項(xiàng)新特性,它可以讓開發(fā)者在無(wú)需編寫類組件的情況下,使用狀態(tài)和其他React特性。
-
使用React Hooks,開發(fā)者可以在函數(shù)組件中使用狀態(tài)(state)、副作用(side effects)和上下文(context),而不再需要編寫類組件。這樣可以使代碼更簡(jiǎn)潔、易于理解和測(cè)試。
-
React Hooks提供了一些預(yù)定義的鉤子函數(shù),如useState、useEffect、useContext等,開發(fā)者可以通過調(diào)用這些鉤子函數(shù)來(lái)使用相應(yīng)的特性。
React Hooks具特點(diǎn)
React Hooks提供了一種更簡(jiǎn)單、更直觀的方式來(lái)編寫React組件。它使得狀態(tài)管理、副作用處理和上下文使用更加簡(jiǎn)單和可靠,并提供了性能優(yōu)化和生命周期簡(jiǎn)化的特性。使用React Hooks可以提高開發(fā)效率、代碼可讀性和可維護(hù)性。
函數(shù)式組件
React Hooks允許在函數(shù)組件中使用狀態(tài)和其他React特性,而不再需要編寫類組件。這使得組件的代碼更簡(jiǎn)潔、易于理解和測(cè)試。
狀態(tài)管理
useState鉤子函數(shù)使得在函數(shù)組件中使用狀態(tài)變得非常簡(jiǎn)單。它返回一個(gè)狀態(tài)值和一個(gè)更新該狀態(tài)值的函數(shù),開發(fā)者可以直接在函數(shù)組件中聲明和使用狀態(tài)。
副作用處理
useEffect鉤子函數(shù)用于處理副作用,如訂閱事件、網(wǎng)絡(luò)請(qǐng)求等。它可以在組件渲染后執(zhí)行副作用操作,并在組件卸載前清理副作用。這使得副作用的管理更加簡(jiǎn)單和可靠。
上下文使用
useContext鉤子函數(shù)使得在函數(shù)組件中使用上下文變得容易。開發(fā)者可以訪問由React的Context API提供的上下文值,而不再需要使用高階組件或渲染屬性模式。
可重用性
useRef鉤子函數(shù)用于在函數(shù)組件中創(chuàng)建可變的引用。它可以用來(lái)保存任意可變值,類似于類組件中的實(shí)例變量。這使得在函數(shù)組件中實(shí)現(xiàn)一些需要保持狀態(tài)的邏輯更加方便。
性能優(yōu)化
useMemo和useCallback鉤子函數(shù)用于在函數(shù)組件中緩存計(jì)算結(jié)果和回調(diào)函數(shù)。它們可以避免重復(fù)計(jì)算和回調(diào)函數(shù)的重復(fù)創(chuàng)建,提高性能。
簡(jiǎn)化生命周期
React Hooks簡(jiǎn)化了組件的生命周期管理。不再需要編寫繁瑣的生命周期方法,Hooks提供了更直觀和靈活的方式來(lái)處理組件的行為。
常用的React Hooks
React Hooks是React的一項(xiàng)重要特性,它為開發(fā)者提供了更多的靈活性和便利性,使得開發(fā)React應(yīng)用更加簡(jiǎn)單和高效。使用React Hooks可以讓開發(fā)者更自由地組織代碼,避免類組件中的繁瑣的生命周期方法,并提供更好的性能和可測(cè)試性。但需要注意的是,Hooks只能在函數(shù)組件的頂層調(diào)用,不能在循環(huán)、條件語(yǔ)句或嵌套函數(shù)中調(diào)用。React Hooks包括以下常用的函數(shù):
useState
用于在函數(shù)組件中定義和更新狀態(tài)。用于在函數(shù)組件中使用狀態(tài)。它返回一個(gè)狀態(tài)值和一個(gè)更新該狀態(tài)值的函數(shù)。
以下是一個(gè)使用useState的案例:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
export default Counter;
-
在上述例子中,我們使用了useState鉤子函數(shù)來(lái)聲明一個(gè)名為count的狀態(tài)變量和一個(gè)名為setCount的函數(shù),用于更新該狀態(tài)變量。 -
在Counter組件中,我們將count的初始值設(shè)置為0。然后,我們?cè)诮M件的返回值中使用count來(lái)展示當(dāng)前的計(jì)數(shù)值,并使用setCount來(lái)更新該計(jì)數(shù)值。 -
通過點(diǎn)擊"Increment"按鈕,我們調(diào)用setCount函數(shù)并傳入count + 1,從而將計(jì)數(shù)值增加1。同樣,點(diǎn)擊"Decrement"按鈕,我們調(diào)用setCount函數(shù)并傳入count - 1,從而將計(jì)數(shù)值減少1。 -
每次調(diào)用setCount函數(shù)時(shí),React會(huì)重新渲染組件,并更新展示的計(jì)數(shù)值。
這個(gè)例子展示了如何使用useState來(lái)在函數(shù)組件中管理狀態(tài)。useState返回的狀態(tài)值和更新函數(shù)可以在組件的其他地方使用,使得狀態(tài)的管理變得非常簡(jiǎn)單和直觀。
useEffect
用于在函數(shù)組件中執(zhí)行副作用操作,比如訂閱事件、發(fā)送網(wǎng)絡(luò)請(qǐng)求等。可以在組件渲染后執(zhí)行副作用操作,并在組件卸載前清理副作用。
以下是一個(gè)使用React Hooks的useEffect的示例:
import React, { useState, useEffect } from 'react';
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default App;
-
在這個(gè)例子中,我們使用useState來(lái)創(chuàng)建一個(gè)名為count的狀態(tài)變量,并使用setCount函數(shù)來(lái)更新它的值。然后,我們使用useEffect來(lái)監(jiān)視count變量的變化,并在count變化時(shí)更新文檔標(biāo)題。每當(dāng)用戶點(diǎn)擊“Increment”按鈕時(shí),count的值會(huì)增加1,然后useEffect會(huì)觸發(fā),更新文檔標(biāo)題。
-
這是一個(gè)簡(jiǎn)單的例子,但它展示了useEffect的用法:在組件渲染后執(zhí)行副作用操作。在這個(gè)例子中,副作用操作是更新文檔標(biāo)題,但useEffect還可以用于執(zhí)行其他副作用操作,例如發(fā)送網(wǎng)絡(luò)請(qǐng)求、訂閱事件等。
useContext
用于在函數(shù)組件中訪問共享的狀態(tài),而不需要通過props傳遞。用于在函數(shù)組件中使用上下文。可以訪問由React的Context API提供的上下文值。
以下是一個(gè)使用React Hooks的useContext的示例:
import React, { useState, useContext, createContext } from 'react';
// 創(chuàng)建一個(gè)上下文對(duì)象
const ThemeContext = createContext();
// 父組件
const App = () => {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
};
// 子組件
const Toolbar = () => {
const { theme, setTheme } = useContext(ThemeContext);
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<div>
<button onClick={toggleTheme}>Toggle Theme</button>
<p>Current Theme: {theme}</p>
</div>
);
};
export default App;
-
在這個(gè)例子中,我們使用createContext創(chuàng)建了一個(gè)名為ThemeContext的上下文對(duì)象。然后,我們?cè)诟附M件App中使用useState創(chuàng)建了一個(gè)名為theme的狀態(tài)變量,并使用setTheme函數(shù)來(lái)更新它的值。將theme和setTheme通過ThemeProvider提供給子組件。
-
在子組件Toolbar中,我們使用useContext來(lái)獲取ThemeContext的值,即theme和setTheme。然后,我們?cè)趖oggleTheme函數(shù)中根據(jù)當(dāng)前的theme值來(lái)切換主題。當(dāng)用戶點(diǎn)擊“Toggle Theme”按鈕時(shí),theme的值會(huì)切換為“l(fā)ight”或“dark”,并通過setTheme函數(shù)更新。然后,我們?cè)诮缑嫔巷@示當(dāng)前的主題。
-
這個(gè)例子展示了useContext的用法:通過創(chuàng)建和使用上下文對(duì)象,我們可以在組件之間共享數(shù)據(jù)和函數(shù),而不需要通過props傳遞。
useReducer
類似于useState,但可以處理復(fù)雜的狀態(tài)邏輯。
以下是一個(gè)使用React Hooks的useReducer的示例:
import React, { useReducer } from 'react';
// 初始化狀態(tài)
const initialState = { count: 0 };
// reducer函數(shù)
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
};
// 組件
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
};
export default Counter;
-
在這個(gè)例子中,我們使用useReducer來(lái)創(chuàng)建一個(gè)名為state的狀態(tài)變量和一個(gè)名為dispatch的函數(shù)。useReducer接收一個(gè)reducer函數(shù)和一個(gè)初始狀態(tài)initialState作為參數(shù)。reducer函數(shù)根據(jù)action的類型來(lái)更新狀態(tài),并返回一個(gè)新的狀態(tài)對(duì)象。
-
在組件中,我們顯示了當(dāng)前的計(jì)數(shù)值state.count,并使用dispatch函數(shù)來(lái)分發(fā)不同的action。當(dāng)用戶點(diǎn)擊“Increment”按鈕時(shí),我們使用dispatch({ type: 'increment' })來(lái)觸發(fā)一個(gè)increment類型的action,reducer函數(shù)會(huì)將計(jì)數(shù)值加1。當(dāng)用戶點(diǎn)擊“Decrement”按鈕時(shí),我們使用dispatch({ type: 'decrement' })來(lái)觸發(fā)一個(gè)decrement類型的action,reducer函數(shù)會(huì)將計(jì)數(shù)值減1。
-
這個(gè)例子展示了useReducer的用法:通過reducer函數(shù)和dispatch函數(shù),我們可以在組件中管理復(fù)雜的狀態(tài)邏輯。在這個(gè)例子中,我們使用useReducer來(lái)管理計(jì)數(shù)器的狀態(tài),但它也可以用于管理更復(fù)雜的狀態(tài),例如表單數(shù)據(jù)、列表數(shù)據(jù)等。
useRef
用于在函數(shù)組件中創(chuàng)建可變的引用。可以用來(lái)保存任意可變值,類似于類組件中的實(shí)例變量。
以下是一個(gè)使用React Hooks的useRef的示例:
import React, { useRef } from 'react';
const TextInput = () => {
const inputRef = useRef();
const handleFocus = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>Focus</button>
</div>
);
};
export default TextInput;
-
在這個(gè)例子中,我們使用useRef來(lái)創(chuàng)建一個(gè)名為inputRef的引用。在組件中,我們將input元素的ref屬性設(shè)置為inputRef,這樣我們就可以通過inputRef.current來(lái)訪問input元素。
-
在handleFocus函數(shù)中,我們使用inputRef.current.focus()來(lái)聚焦input元素。當(dāng)用戶點(diǎn)擊“Focus”按鈕時(shí),handleFocus函數(shù)會(huì)被調(diào)用,從而將焦點(diǎn)設(shè)置到input元素上。
這個(gè)例子展示了useRef的用法:通過引用,我們可以在函數(shù)組件中獲取和操作DOM元素。在這個(gè)例子中,我們使用useRef來(lái)獲取input元素,并通過引用來(lái)聚焦該元素。除了DOM元素,useRef還可以用于存儲(chǔ)和訪問任意的可變值,類似于類組件中的實(shí)例變量。
useMemo
用于在函數(shù)組件中緩存計(jì)算結(jié)果。可以避免重復(fù)計(jì)算,提高性能,以優(yōu)化性能。
以下是一個(gè)使用React Hooks的useMemo的示例:
import React, { useState, useMemo } from 'react';
const ExpensiveCalculation = () => {
// 假設(shè)這是一個(gè)昂貴的計(jì)算函數(shù)
const calculate = (a, b) => {
console.log('Calculating...');
return a + b;
};
const [num1, setNum1] = useState(0);
const [num2, setNum2] = useState(0);
// 使用useMemo來(lái)緩存計(jì)算結(jié)果
const result = useMemo(() => calculate(num1, num2), [num1, num2]);
return (
<div>
<input type="number" value={num1} onChange={(e) => setNum1(Number(e.target.value))} />
<input type="number" value={num2} onChange={(e) => setNum2(Number(e.target.value))} />
<p>Result: {result}</p>
</div>
);
};
export default ExpensiveCalculation;
-
在這個(gè)例子中,我們假設(shè)calculate函數(shù)是一個(gè)昂貴的計(jì)算函數(shù),它接收兩個(gè)參數(shù)并返回它們的和。我們使用useState來(lái)創(chuàng)建兩個(gè)狀態(tài)變量num1和num2,分別表示兩個(gè)輸入框中的值。
-
然后,我們使用useMemo來(lái)緩存計(jì)算結(jié)果。useMemo接收一個(gè)回調(diào)函數(shù)和一個(gè)依賴數(shù)組,只有當(dāng)依賴數(shù)組中的值發(fā)生變化時(shí),才會(huì)重新計(jì)算回調(diào)函數(shù)的結(jié)果。在這個(gè)例子中,我們將calculate函數(shù)和num1、num2作為依賴數(shù)組,這樣只有當(dāng)num1或num2的值發(fā)生變化時(shí),才會(huì)重新計(jì)算calculate的結(jié)果。
-
最后,我們?cè)诮缑嫔巷@示計(jì)算結(jié)果result。
這個(gè)例子展示了useMemo的用法:通過緩存計(jì)算結(jié)果,我們可以避免在每次渲染時(shí)都執(zhí)行昂貴的計(jì)算操作。在這個(gè)例子中,我們使用useMemo來(lái)緩存計(jì)算結(jié)果,以提高性能。
useCallback
用于在函數(shù)組件中緩存回調(diào)函數(shù)。可以避免回調(diào)函數(shù)的重復(fù)創(chuàng)建,提高性能,以優(yōu)化性能。
以下是一個(gè)使用React Hooks的useCallback的示例:
import React, { useState, useCallback } from 'react';
const Button = () => {
const [count, setCount] = useState(0);
// 使用useCallback來(lái)緩存回調(diào)函數(shù)
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
export default Button;
-
在這個(gè)例子中,我們使用useState來(lái)創(chuàng)建一個(gè)名為count的狀態(tài)變量,表示計(jì)數(shù)值。
-
然后,我們使用useCallback來(lái)緩存回調(diào)函數(shù)handleClick。useCallback接收一個(gè)回調(diào)函數(shù)和一個(gè)依賴數(shù)組,只有當(dāng)依賴數(shù)組中的值發(fā)生變化時(shí),才會(huì)重新創(chuàng)建回調(diào)函數(shù)。在這個(gè)例子中,我們將count作為依賴數(shù)組,這樣只有當(dāng)count的值發(fā)生變化時(shí),才會(huì)重新創(chuàng)建handleClick回調(diào)函數(shù)。
-
最后,我們?cè)诮缑嫔巷@示計(jì)數(shù)值count,并將handleClick回調(diào)函數(shù)綁定到按鈕的onClick事件。
這個(gè)例子展示了useCallback的用法:通過緩存回調(diào)函數(shù),我們可以避免在每次渲染時(shí)都創(chuàng)建新的回調(diào)函數(shù)。在這個(gè)例子中,我們使用useCallback來(lái)緩存handleClick回調(diào)函數(shù),以提高性能。
useLayoutEffect
類似于useEffect,但在DOM更新之后同步執(zhí)行,用于處理DOM操作的副作用。使用useLayoutEffect的一個(gè)常見案例是在DOM渲染完成后執(zhí)行一些副作用操作,例如更新DOM元素的位置或大小。
下面是一個(gè)使用useLayoutEffect的示例:
import React, { useState, useLayoutEffect } from 'react';
const MyComponent = () => {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};
// 添加resize事件監(jiān)聽器
window.addEventListener('resize', handleResize);
// 初始化時(shí)獲取一次窗口寬度
handleResize();
// 清除resize事件監(jiān)聽器
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>窗口寬度: {width}px</div>;
};
export default MyComponent;
-
在上面的示例中,useLayoutEffect鉤子被用來(lái)在組件渲染后添加一個(gè)resize事件監(jiān)聽器,并在組件卸載前清除監(jiān)聽器。在handleResize回調(diào)函數(shù)中,通過調(diào)用setWidth來(lái)更新組件的狀態(tài),從而觸發(fā)重新渲染。
-
這個(gè)例子中,我們使用了一個(gè)空的依賴數(shù)組作為useLayoutEffect的第二個(gè)參數(shù),這意味著只有在組件初始化時(shí)才會(huì)執(zhí)行一次useLayoutEffect的回調(diào)函數(shù)。這樣我們就可以確保只添加一次resize事件監(jiān)聽器,并且在組件卸載時(shí)正確地清除它。
-
通過使用useLayoutEffect來(lái)處理副作用操作,我們可以確保在DOM渲染完成后執(zhí)行這些操作,從而避免可能導(dǎo)致布局問題的延遲效果。
useImperativeHandle
用于在父組件中訪問子組件的方法和屬性。使用useImperativeHandle的一個(gè)常見案例是在子組件中暴露父組件的某些方法或?qū)傩裕员愀附M件可以直接調(diào)用子組件的方法或訪問子組件的屬性。
下面是一個(gè)使用useImperativeHandle的示例:
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const ChildComponent = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focusInput: () => {
inputRef.current.focus();
},
getValue: () => {
return inputRef.current.value;
}
}));
return <input ref={inputRef} />;
});
const ParentComponent = () => {
const childRef = useRef(null);
const handleClick = () => {
childRef.current.focusInput();
};
const handleGetValue = () => {
const value = childRef.current.getValue();
console.log('子組件的值:', value);
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleClick}>聚焦子組件輸入框</button>
<button onClick={handleGetValue}>獲取子組件的值</button>
</div>
);
};
export default ParentComponent;
-
在上面的示例中,我們使用useRef來(lái)創(chuàng)建一個(gè)引用,然后將其傳遞給子組件的ref屬性。在子組件中,我們使用useImperativeHandle來(lái)定義父組件可以調(diào)用的方法或訪問的屬性。在這個(gè)例子中,我們暴露了一個(gè)focusInput方法和一個(gè)getValue方法,分別用于聚焦子組件的輸入框和獲取輸入框的值。
-
在父組件中,我們可以通過調(diào)用childRef.current.focusInput()來(lái)聚焦子組件的輸入框,或者通過調(diào)用childRef.current.getValue()來(lái)獲取子組件輸入框的值。
-
通過使用useImperativeHandle,我們可以更靈活地在父組件中操作子組件,同時(shí)保持良好的封裝性。這在需要直接與子組件進(jìn)行交互的情況下非常有用。
useDebugValue
用于在React開發(fā)者工具中顯示自定義的鉤子名稱。使用useDebugValue的一個(gè)常見案例是在自定義的Hook中為開發(fā)者提供更好的調(diào)試工具,例如在React開發(fā)者工具中顯示有用的自定義標(biāo)簽。
下面是一個(gè)使用useDebugValue的示例:
import React, { useState, useEffect, useDebugValue } from 'react';
const useFetchData = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
} catch (error) {
console.error(error);
}
};
fetchData();
}, [url]);
// 使用useDebugValue在React開發(fā)者工具中顯示自定義標(biāo)簽
useDebugValue(loading ? 'Loading...' : data ? 'Data loaded' : 'No data');
return { data, loading };
};
const MyComponent = () => {
const { data, loading } = useFetchData('https://api.example.com/data');
if (loading) {
return <div>Loading...</div>;
}
if (!data) {
return <div>No data available</div>;
}
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};
export default MyComponent;
-
在上面的示例中,我們創(chuàng)建了一個(gè)自定義的Hook useFetchData,它會(huì)從指定的URL獲取數(shù)據(jù)并返回?cái)?shù)據(jù)和加載狀態(tài)。在useFetchData中,我們使用了useDebugValue來(lái)為開發(fā)者提供有用的調(diào)試信息。在React開發(fā)者工具中,我們可以看到loading狀態(tài)是"Loading..."、data狀態(tài)是"Data loaded"或者是"No data"。
-
通過使用useDebugValue,我們可以更好地了解Hook在不同狀態(tài)下的運(yùn)行情況,以及為開發(fā)者提供更好的調(diào)試工具。這對(duì)于自定義Hook的開發(fā)和調(diào)試非常有幫助。
除了上述常用的Hooks,還可以通過自定義Hooks來(lái)實(shí)現(xiàn)更多的功能和復(fù)用邏輯。自定義Hooks可以使用任何已有的Hooks,并以"use"開頭命名,以便于其他開發(fā)者識(shí)別和使用。
