基本概念
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 的生命周期函數,你可以把
useEffectHook 看做componentDidMount,componentDidUpdate和componentWillUnmount這三個函數的組合。與
componentDidMount或componentDidUpdate不同,使用useEffect調度的 effect 不會阻塞瀏覽器更新屏幕,這讓你的應用看起來響應更快。大多數情況下,effect 不需要同步地執行。在個別情況下(例如測量布局),有單獨的useLayoutEffectHook 供你使用,其 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方法進行了封裝
