基本概念
Hook 是能讓你在函數組件中“鈎入” React 特性的函數,它們名字通常都以 use
開始。
Hook 使用了 JavaScript 的閉包機制,而不用在 JavaScript 已經提供了解決方案的情況下,還引入特定的 React API。
State Hook
function ExampleWithManyStates() {
// 聲明多個 state 變量
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '學習 Hook' }]);
State 變量可以很好地存儲對象和數組,因此,你仍然可以將相關數據分為一組。然而,不像 class 中的 this.setState
,更新 state 變量總是替換它而不是合並它。
函數式更新
如果新的 state 需要通過使用先前的 state 計算得出,那么可以將函數傳遞給 setState
。該函數將接收先前的 state,並返回一個更新后的值。
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
與 class 組件中的
setState
方法不同,useState
不會自動合並更新對象。你可以用函數式的setState
結合展開運算符來達到合並更新對象的效果。setState(prevState => { // 也可以使用 Object.assign return {...prevState, ...updatedValues}; });
Effect Hook
通過使用這個 Hook,你可以告訴 React 組件需要在渲染后執行某些操作。
你可能會更容易接受 effect 發生在“渲染之后”這種概念,不用再去考慮“掛載”還是“更新”。
如果你熟悉 React class 的生命周期函數,你可以把
useEffect
Hook 看做componentDidMount
,componentDidUpdate
和componentWillUnmount
這三個函數的組合。與
componentDidMount
或componentDidUpdate
不同,使用useEffect
調度的 effect 不會阻塞瀏覽器更新屏幕,這讓你的應用看起來響應更快。大多數情況下,effect 不需要同步地執行。在個別情況下(例如測量布局),有單獨的useLayoutEffect
Hook 供你使用,其 API 與useEffect
相同。
如果你的 effect 返回一個函數,React 將會在執行清除操作時調用它。 React 會在執行當前 effect 之前對上一個 effect 進行清除。
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
Hook 允許我們按照代碼的用途分離他們, 而不是像生命周期函數那樣。
在某些情況下,每次渲染后都執行清理或者執行 effect 可能會導致性能問題。在 class 組件中,我們可以通過在 componentDidUpdate
中添加對 prevProps
或 prevState
的比較邏輯解決:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
性能優化
你可以通知 React 跳過對 effect 的調用,只要傳遞數組作為 useEffect
的第二個可選參數即可:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新
如果想執行只運行一次的 effect(僅在組件掛載和卸載時執行),可以傳遞一個空數組(
[]
)作為第二個參數。
要記住 effect 外部的函數使用了哪些 props 和 state 很難。這也是為什么 通常你會想要在 effect 內部 去聲明它所需要的函數。
Hook 規則
- 只能在函數最外層調用 Hook。不要在循環、條件判斷或者子函數中調用。
- 只能在 React 的函數組件中調用 Hook。不要在其他 JavaScript 函數中調用。
那么 React 怎么知道哪個 state 對應哪個 useState
?答案是 React 靠的是 Hook 調用的順序。因為我們的示例中,Hook 的調用順序在每次渲染中都是相同的,所以它能夠正常工作:
// ------------
// 首次渲染
// ------------
useState('Mary') // 1. 使用 'Mary' 初始化變量名為 name 的 state
useEffect(persistForm) // 2. 添加 effect 以保存 form 操作
useState('Poppins') // 3. 使用 'Poppins' 初始化變量名為 surname 的 state
useEffect(updateTitle) // 4. 添加 effect 以更新標題
// -------------
// 二次渲染
// -------------
useState('Mary') // 1. 讀取變量名為 name 的 state(參數被忽略)
useEffect(persistForm) // 2. 替換保存 form 的 effect
useState('Poppins') // 3. 讀取變量名為 surname 的 state(參數被忽略)
useEffect(updateTitle) // 4. 替換更新標題的 effect
// ...
如果我們想要有條件地執行一個 effect,可以將判斷放到 Hook 的內部
自定義 Hook
自定義 Hook 是一個函數,其名稱以 “use” 開頭,函數內部可以調用其他的 Hook。以use開頭的原因是react需要檢查你的 Hook 是否違反了 Hook 的規則。
自定義 Hook 不需要具有特殊的標識。我們可以自由的決定它的參數是什么,以及它應該返回什么(如果需要的話)。
自定義 Hook 是一種自然遵循 Hook 設計的約定,而並不是 React 的特性。
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
本質上是用一個公共函數將多個Hook方法進行了封裝