【譯】React Hooks的5個重要原則

  • 1987字
  • 10分鐘
  • 2024-07-27

在當下,React 無疑仍然是創建前端應用程序的最受歡迎選擇之一。這並不是因為它沒有缺點,而是因為 React 多年來積累了龐大的社區和巨大的人氣。

很難想象沒有 Hook 的 React,但不幸的是,我們經常看到開發人員過度使用它們。結果,他們不得不解決依賴數組、無用的重新渲染以及它們造成的所有混亂問題,因為他們內心深處擔心,在 React 中,只有使用 Hook 才能完成所有事情。

在本文中,我將討論每個新 React 程式設計師應該知道的五個原則,以改進和簡化他們的代碼。

1. 不是每個函數都必須是一個 Hook

讓我們從基礎開始,先看看定義

Hook 使用 JavaScript 函數定義,但它們代表一種特殊的可重用 UI 邏輯,並且它們在調用位置上有限制。當你使用 Hook 時,需要遵循 Hook 的規則。

Hooks 看起來像函數,但有一些區別:

  • Hooks 只能在函數組件或自訂 Hook 中使用。
  • Hook 的名稱總是以“use”開頭,後跟一個大寫字母。
  • 如果自訂 Hook 內部不包含對其他 Hook 的調用,它就是一個函數,而不是一個 Hook。這很重要,因為它有助於確定是否有一些狀態邏輯或副作用在其中。

讓我們創建一個名為 useBoolean 的簡單自訂 Hook,以滿足這些要求。我們可以用它來打開/關閉面板、對話框、顯示/隱藏元素等。

如果查看官方文檔,您會發現建議將 Hook 返回的任何函數包裝在 useCallback 中,我們也將這樣做。

1
interface ICallbacks {
2
setFalse: () => void;
3
setTrue: () => void;
4
toggle: () => void;
5
}
6
7
const useBoolean = (initialValue: boolean): [boolean, ICallbacks] => {
8
const [value, setValue] = useState(initialValue);
9
10
const setFalse = useCallback(() => {
11
setValue(false);
12
}, []);
13
14
const setTrue = useCallback(() => {
15
setValue(true);
16
}, []);
17
18
const toggle = useCallback(() => {
19
setValue((curValue) => !curValue);
20
}, []);
21
22
return [value, { setFalse, setTrue, toggle }];
23
};

記住基礎知識後,我們可以更深入地了解一些細微差別。

2. 了解重新渲染

你可能會問為什麼。

理解 React 如何工作以及當您通過 setter 函數更改組件的狀態時會發生什麼,這一點很重要。乍一看,您似乎改變了狀態,結果必須立即出現,但事實是這樣嗎?

當你知道改變狀態發生了什麼時,就更容易理解為什麼以及何時 useEffect 或其他帶依賴數組的 Hook 會被觸發。

讓我們看看一個簡單的例子。想象一下,我們第一次按下按鈕並調用 onChangeText,傳遞值“newValue”。

1
const [text, setText] = useState("defaultValue");
2
3
const onChangeText = (value: string) => {
4
// value 等於 "newValue"
5
setText(value);
6
7
console.log(text); // 這裡的值會是什麼?
8
};

看起來我們應該在控制台中看到“newValue”,但實際上會是“defaultValue”。為什麼?因為新值只有在重新渲染後才可用。

有必要看到發生的步驟:

  1. 我們通過 setter 函數改變狀態,告訴 React 採取行動。
  2. 渲染。React 調用你的組件以計算新的 JSX,這將被返回。
  3. 提交。計算完變化後,React 將修改 DOM;最小的動作將被應用。
  4. 在前面的步驟之後,您將在屏幕上看到視覺變化(“瀏覽器渲染”)。

每次你想改變狀態中的值時,都需要記住這些步驟每次都會完成。

3. useState 並不總是正確的答案

在 React 中,我們有兩種管理組件狀態的方法——useState 和 useReducer。第二種方法不太流行,因為它適用於狀態中更複雜的對象,老實說,對於新程式設計師來說,乍一看它看起來太複雜了,但事實並非如此。

然而,useState 看起來非常簡單易懂,所以新程式設計師往往使用它的次數超過了實際需要。

它旨在根據使用者互動來管理重新繪製組件的狀態。如果你想記住一些內容而不進行渲染,可能不應該將其放在狀態中。useRef 會是更好的選擇。

如果:

  • 你想在重新渲染期間記住一些值而不向使用者展示它們。
  • 你已經在狀態中有數據,或者你通過 props 接收它,但需要轉換它;你不需要將新值保存在新的 useState 對象中,創建一個新變量並操作它而不會觸發無用的重新渲染。

你需要將值保存在狀態中,如果:

  • 你想在值改變時重新繪製組件;最常見的例子是顯示/隱藏面板、旋轉器、錯誤消息以及修改數組。

將你的代碼簡化如下:

1
/**
2
以下代碼導致無用的重新渲染和不必要的 useEffect 使用。
3
當 name 或 description 變化是,React 會重新渲染組件
4
**/
5
6
const [name, setName] = useState("name");
7
const { description, index } = props;
8
const [fullName, setFullName] = useState("");
9
10
useEffect(() => {
11
setFullName(`${name} - ${description}`);
12
}, [name, description]);

更改為:

1
/**
2
我們可以使用 React 的默認行為,在 name
3
或 description 更改時獲取正確的值,
4
而不觸發一次更多的重新渲染。
5
**/
6
const [name, setName] = useState();
7
const { description, index } = props;
8
const nameWithDescription = `${name} - ${description}`;

4. 使用 useEffect 時需要非常小心

useEffect 是一個 React Hook,它讓你可以同步一個組件和外部系統。

讓我們看看官方文檔

但實際上,我們使用 useEffect 的次數遠超實際需要。它非常適合在組件掛載時獲取數據,但不幸的是,新程式設計師傾向於使用這個 Hook 來改變狀態,這不是最好的解決方案。

如果發現自己在一個組件內部寫一個又一個 useEffect,請停下來審查代碼。通常你不需要它們,而且你可以很快消除它們。

如果符合以下情況則不需要 useEffect:

  • 你需要處理使用者事件(click);如果你知道哪些動作會觸發某些邏輯,不要使用 useEffect 來調用該邏輯。
  • 你需要轉換數據以進行渲染,例如從狀態或 props 中連接字符串。將響應性值或邏輯放入依賴數組中可能會導致 useEffect 被過於頻繁地調用,導致無限迴圈。

如果出現以下情況,您需要選擇 useEffect:

  • 你想在組件掛載時獲取數據,設置間隔,並用它來同步狀態與其他系統

5. 不要害怕 useRef

不幸的是,useRef 被低估了。它不是最流行的 Hook 之一,但它非常有用。知道如何以及在哪裡使用它可以取得很好的效果。

讓我們從基礎開始。

useRef 是一個 React Hook,它讓你可以引用不需要渲染的值。——來自 React 官方文檔。

無論你是在創建一個引用 DOM 中的節點的 JavaScript 對象還是一個簡單的值,React 會記住你通過 useRef 創建的值,並且它不會在重新渲染期間丟失。

它給我們帶來了什麼?

  • 我們可以輕鬆訪問 DOM 中的元素。例如,你可以獲取輸入字段的值,聚焦到特定元素,獲取它們的高度和寬度,滾動到屏幕的特定部分等。
  • 你可以記住任何你需要的數據而不重新渲染組件。例如,如果你需要一個計數器或計時器,選擇 useRef 而不是 useState。

例子:

1
// 引用一個數字
2
const refCount= useRef(0);
3
// 引用一個輸入元素
4
const refInputField = useRef(null);
5
6
/**
7
要訪問該值,你需要使用 current 屬性。
8
useRef 不會觸發重新渲染,所以你可以在 useEffect 內部使用它
9
而不用擔心依賴數組
10
*/
11
12
const onClick = () => {
13
refCount.current = refCount.current + 1;
14
refInputField.current.focus();
15
}
16
17
return (
18
<>
19
<button onClick={onClick}>
20
Click me!
21
</button>
22
<input ref={refInputField}></input>
23
</>
24
);