掛鉤規則
在使用 Hooks 時,有一些原則需要遵守,以避免錯誤和異常的行為:
掛鉤僅能在組件中運行
掛鉤只能從 React 組件中調用。也可以將它們添加到自定義掛鉤中,然後將它們添加到組件中。 掛鉤不是常規的 JavaScript,它們是一種 React 模式,可建立以模組化方式整合到其他庫中。
將功能分解為多個掛鉤是個好主意
拆分功能為多個掛鉤,除了使代碼更易於閱讀外,還有另一個好處。由於 Hook 是按順序調用的,因此最好使它們保持較小的功能。調用後,React 會將 Hooks 的值保存在陣列中,所以比較好追蹤較小功能的運作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const Counter = () => {
const [count, setCount] = useState(0);
const [checked, toggle] = useState(false);
useEffect(() => {
...
}, [checked]);
useEffect(() => {
...
}, []);
useEffect(() => {
...
}, [count]);
return ( ...)
};每個 Hook 的調用和渲染順序都相同:
[count, checked, DependencyArray, DependencyArray, DependencyArray]
掛鉤只能在頂層調用
掛鉤應在 React 函式的頂層使用。它們不能放入條件語句,循環或嵌套函式中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const Counter = () => {
const [count, setCount] = useState(0);
if (count > 5) {
const [checked, toggle] = useState(false);
}
useEffect(() => {
...
});
if (count > 5) {
useEffect(() => {
...
});
}
useEffect(() => {
...
});
return ( ... );
};當我們在 if 語句中使用 useState 時,僅當 count 大於 5 時才應調用該掛鉤。拋出的陣列值,有時是 [count, checked, DependencyArray, 0, DependencyArray],有時則會是 [count, DependencyArray, 1]。陣列中的 effect 索引對 React 很重要,這是 React 保存 effect 值的方式。
這並不是說我們不能在 React 應用程序中使用條件語句。我們只需要以不同的方式組織這些條件。我們可以在掛鉤中嵌套 if 語句,循環和其他條件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const Counter = () => {
const [count, setCount] = useState(0);
const [checked, toggle] = useState(count => (count < 5) ? undefined : false);
useEffect(() => {
...
});
useEffect(() => {
if (count < 5) return;
...
});
useEffect(() => {
...
});
return ( ... );
};第 3 行,count 小於 5 時,checked 值為 undefined。 將條件嵌套在掛鉤內部而將該掛鉤保留在頂層,但是結果是相似的。 第 9 行的 effect 執行相同的規則,如果 count 小於 5,則 return 語句將阻止 effect 繼續執行。這將使掛鉤陣列值保持一致 [count,checked,DependencyArray,DependencyArray,DependencyArray]。
與條件語句邏輯一樣,當你需要在掛鉤中嵌套異步行為。 useEffect 將函式作為第一個參數,而不是 Promise。因此,您不能將異步函式用作第一個參數:
1
useEffect(async () => {})
但是,你可以在參數函式內部嵌套一個異步函數:
1
2
3
4
5
6useEffect(() => {
const fn = async () => {
await SomePromise();
};
fn();
});我們創建了一個變數 fn,以處理 async/await,然後執行該函式。 您也可以使用匿名函式:
1
2
3
4
5useEffect(() => {
(async () => {
await SomePromise();
})();
});
如果遵循上述的這些規則,你可以避免 React Hooks 的一些常見問題。
使用 useReducer 改進代碼
思考一下 Checkbox 複選框組件,該組件擁有簡單的狀態,複選框值只有 “checked” 與 “not checked”。 其中,checked 是狀態值,而 setChecked 是用於更改狀態的函式。 首次渲染組件時,checked 的值為 false:
1 | <div id="react-container" /> |
效果不錯,但是此功能如果用在不同的的區域可能會引起一些維護上的困擾。
1 | onChange={() => setChecked(checked => !checked)} |
乍看下感覺還可以,這裡會有甚麼問題嗎?我們送出一個接受當前 checked 值並返回相反的!checked 的函式。 開發人員有可能會送出錯誤的函式並破壞整個過程。與其以這種方式進行處理,為什麼不提供一個功能函式呢?
讓我們添加一個名為 toggle 的函式,該函式將執行與 setChecked 相同的操作,並返回與 checked 當前值相反的值:
1 | const Checkbox = () => { |
這個好些了。onChange 設置為可預測的值:toggle 函式。我們知道每次使用該函式時,它將執行預定地操作,產生更可預測的結果。
這裡我們在 toggle 函式中使用了setChecked 的涵式:
1 | setChecked(checked => !checked); |
我們現在要使用另一個方式來引用這個函式,checked =>!checked,它是一個 reducer。reducer 函式最簡單的定義是,它接受當前狀態並返回新狀態。 如果 checked 為 false,則應返回相反的 true。
在最初我們將此行為硬編碼為 onChange 事件,我們現在可以將邏輯簡化為 reducer 函式,該邏輯將始終產生相同的結果。
我們將使用 useReducer 代替組件中的 useState:
1 | const Checkbox = () => { |
第 2 行,useReducer 接受一個 reducer 函式和初始狀態 false。 然後,將 onChange 函式設置為 toggle,這將會調用 reducer 函式。
我們較早的 reducer,checked =>!checked,就是一個很好的例子。如果將相同的輸入提供給函式,則應預期會有相同的輸出。
這個概念起源於 JavaScript 中的 Array.reduce。reduce 從根本上與 reducer 具有相同的作用:它接受一個函式(這個函式會將所有值都簡化為單一的值)和一個初始值,並返回一個單一的值。
Array.reduce 接受一個 reducer 函式和一個初始值。 對於 numbers 陣列中的每個值,將調用 reducer 直到返回一個值:
1 | const numbers = [28, 34, 67, 68]; |
發送到 Array.reduce 的 reducer 函式接受兩個參數。所以這裡我們也可以將多個參數發送給 useReducer 的 reducer 函式:
1 | const Numbers = () => { |
第 3 行就是這裡的 reducer 函式,接受了兩個參數 number 與 newNumber。在第 7 行調用 setNumber 時就會執行這個 reducer 函式,傳入 30 給 newNumber,然後加到當前的 number 狀態值。現在,每次單擊 h1 時,我們都會將總數加 30。
useReducer 處理複雜狀態
隨著狀態變得越來越複雜,useReducer 可以幫助我們更可預測地處理狀態更新。來看另外一個含有 User 數據的物件:
1 | const firstUser = { |
管理狀態時常見的錯誤是覆蓋狀態:
1 | <button |
第 4 行這樣做將覆蓋 firstUser 的狀態,並將其替換為我們發送給 setUser 函式的內容:{admin:true}。這可以透過 JavaScript 的展開運算子(Spread syntax)解決此問題:展開 user 的當前值,然後覆蓋 admin 值:
1 | <button |
這將採用初始狀態並輸入新的鍵/值:{ admin:true }。我們需要在每個 onClick 中重寫此邏輯,這很容易出錯,也可能會忘記這樣做。現在改用 useReducer:
1 | const User = () => { |
然後,我們發送新的狀態值 newDetails,傳遞給 reducer,並將其推入物件。
1 | <button |
當狀態具有多個子值或下一個狀態取決於上一個狀態時,此模式很有用。