概述
Hook 使你在非 class 的情況下可以使用更多的 React 特性。Hook 是一些可以在函數(shù)組件里鉤入React state 及生命周期等特性的函數(shù)。Hook 就是 JavaScript 函數(shù),但是使用它們會(huì)有兩個(gè)額外的規(guī)則
- 只在最頂層使用 Hook。不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook。
- 只在 React 函數(shù)中調(diào)用 Hook。不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook??梢栽?
- React 的函數(shù)組件中調(diào)用 Hook
- 在自定義 Hook 中調(diào)用其他 Hook
Hook 是一種服用狀態(tài)邏輯的方式,它不復(fù)用 state 本身。事實(shí)上 Hook 的每次調(diào)用都有一個(gè)完全獨(dú)立的 state,因此可以在單個(gè)組件中多次調(diào)用同一個(gè)自定義 Hook。
React 依據(jù) Hook 調(diào)用的順序知道哪個(gè) state 對(duì)應(yīng)哪個(gè) useState
State Hook
在 React 函數(shù)組件上添加內(nèi)部 state
Effect Hook
默認(rèn)情況下,React 會(huì)在每次渲染后調(diào)用副作用函數(shù),包括第一次渲染的時(shí)候。通過(guò)使用 Hook,可以把組件內(nèi)相關(guān)的副作用組織在一起(例如創(chuàng)建訂閱及取消訂閱),而不是要把他們拆分到不同的生命周期函數(shù)里。
- 副作用 => 數(shù)據(jù)獲取,設(shè)置訂閱以及手動(dòng)更改 React 組件中的 DOM 都屬于副作用
在 React 組件中有兩種常見(jiàn)副作用操作:需要清除的和不需要清除的
無(wú)需清除的 effect
在 React 更新 DOM 之后運(yùn)行一些額外的代碼。發(fā)送網(wǎng)絡(luò)請(qǐng)求,手動(dòng)變更 DOM,記錄日志,這些都是常見(jiàn)的無(wú)需清除的操作。React 保證了每次運(yùn)行 effect 的同時(shí),DOM 都已經(jīng)更新完畢。
每次重新渲染,都會(huì)生成新的 effect,替換掉之前的。effect 更像是渲染結(jié)果的一部分,每個(gè) effect 屬于一次特定的渲染
需要清除的 effect
訂閱外部數(shù)據(jù)源這種副作用是需要清除的,可以防止引起內(nèi)存泄漏。副作用函數(shù)可以通過(guò)返回一個(gè)函數(shù)來(lái)指定如何清除副作用
React 何時(shí)清除 effect?React 會(huì)在組件卸載的時(shí)候執(zhí)行清除操作 + effect 在每次渲染的時(shí)候都會(huì)執(zhí)行 => React 會(huì)在執(zhí)行當(dāng)前 effect 之前對(duì)上一個(gè) effect 進(jìn)行清除。
自定義 Hook
自定義 Hook 可以在不增加組件的情況下在組件之間重用一些狀態(tài)邏輯,即可以將組件邏輯提取到可重用的函數(shù)中。自定義 Hook 更像是一種約定而不是功能。
- 自定義 Hook 必須以
use開(kāi)頭。 - 在兩個(gè)組件中使用相同 Hook 不會(huì)共享 state。每次使用自定義 Hook 時(shí),其中所有 state 和副作用都是完全隔離的。
- 每次調(diào)用 Hook,自定義 Hook 都會(huì)獲取獨(dú)立的 state。直接調(diào)用自定義 Hook 時(shí),從 React Dee角度來(lái)看,組件只是調(diào)用了
useState和useEffect
常用 Hook
useState
const [state, setState] = useState(initialState);
setState
setState 可以接收
- 新值,從而更新
state,使得state為新值 - 函數(shù),參數(shù)為先前的
state,state為函數(shù)的返回值const [count, setCounnt] = useState(0); setCounnt(prevCount => prevCount + 1);
useState
- 如果初始
state需要通過(guò)復(fù)雜計(jì)算獲得,則可以傳入一個(gè)函數(shù),在函數(shù)中計(jì)算并返回初始的state,此函數(shù)只在初始渲染時(shí)被調(diào)用const [state, setState] = useState(() => { const initState = someExpensiveComputation(props); return initState; });
useEffect
useEffect 可以看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個(gè)函數(shù)的組合。
通過(guò)傳遞數(shù)組作為 useEffect 的第二個(gè)可選參數(shù)從而跳過(guò) Effect 進(jìn)行性能優(yōu)化。數(shù)組中包含了所有外部作用域中會(huì)隨時(shí)間變化并且在 effect 中使用的變量。
如果想執(zhí)行只運(yùn)行一次的 effect(僅在組件掛載和卸載時(shí)執(zhí)行),可以傳遞一個(gè)空數(shù)組([])作為第二個(gè)參數(shù)。
useEffect 的 effect 的執(zhí)行時(shí)機(jī)
與 componentDidMount、componentDidUpdate 不同的是,在瀏覽器完成布局與繪制之后,傳給 useEffect 的函數(shù)會(huì)延遲調(diào)用,所以不應(yīng)在函數(shù)中執(zhí)行阻塞瀏覽器更新屏幕的操作
useRef
const redContainer = useRef(initValue);
useRef 返回一個(gè)可變的 ref 對(duì)象,其 .current 屬性被初始化為傳入的參數(shù)(initValue)。返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變。
本質(zhì)上,useRef 就像是可以在其 .current 屬性中保存一個(gè)可變值的"盒子"。
useRef() Hook 不僅可以用于 DOM refs,「ref」對(duì)象是一個(gè) current 屬性可變且可以容納任意值的通用容器。
例1
function TextInputWithFocusButton() {
const inputEle = useRef(null);
const onButtonClick = () => {
// current 指向已掛載到 DOM 上的文本輸入元素
inputEle.current.focus();
}
return (
<>
<input ref={inputEle} type='text' />
<button onClick={onButtonClick}>Focus the input</button>
</>
)
}
例2:useRef 可以很方便的保存任何可變值
function Timer() {
const intervalRef = useRef();
useEffect(() => {
intervalRef.current = setinterval(() => {
// ...
});
return () => {
clearInterval(intervalRef.current);
}
});
}
注: 當(dāng) ref 對(duì)象內(nèi)容發(fā)生變化時(shí),useRef 并不會(huì)通知。變更 .current 屬性不會(huì)引起組件重新渲染。如果想要在 React 綁定和解綁 DOM 節(jié)點(diǎn)的 ref 時(shí)運(yùn)行某些代碼,則需要使用 useCallback 來(lái)實(shí)現(xiàn)
useCallback
返回一個(gè) memoized 函數(shù)。
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項(xiàng)數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本,該回調(diào)函數(shù)僅在某個(gè)依賴項(xiàng)改變時(shí)才會(huì)更新。當(dāng)把回調(diào)函數(shù)傳遞給經(jīng)過(guò)優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時(shí),它將非常有用。
useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)
useMemo
返回一個(gè) memoized 值
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把創(chuàng)建函數(shù)和依賴項(xiàng)數(shù)組作為參數(shù)傳入 useMemo,它僅會(huì)在某個(gè)依賴項(xiàng)改變時(shí)才重新計(jì)算 memoized 值。這種優(yōu)化有助于避免在每次渲染時(shí)都進(jìn)行高開(kāi)銷(xiāo)的計(jì)算。
如果沒(méi)有提供依賴項(xiàng),useMemo 在每次渲染時(shí)都會(huì)計(jì)算新的值。可以把 useMemo 作為性能優(yōu)化的手段,但不要把它當(dāng)成語(yǔ)義上的保證
注:傳入 useMemo 的函數(shù)會(huì)在渲染期間執(zhí)行。不要在這個(gè)函數(shù)內(nèi)部執(zhí)行與渲染無(wú)關(guān)的操作,諸如副作用這類的操作屬于 useEffect 的適用范疇
注意:
-
useState不會(huì)自動(dòng)合并并更新對(duì)象??墒鞘褂煤瘮?shù)式的setState達(dá)到合并并更新對(duì)象的效果setState(prevState => ({...prevState, ...updateValues})); - 如果在渲染期間執(zhí)行了高開(kāi)銷(xiāo)的計(jì)算,則可以使用
useMemo來(lái)進(jìn)行了優(yōu)化