3 個 React 狀態管理的規則

3 個 React 狀態管理的規則

瘋狂的技術宅 前端先鋒

翻譯:瘋狂的技術宅
作者:Dmitri Pavlutin
來源:dmitripavlutin
正文共:2630 字
預計閱讀時間:8 分鐘



3 個 React 狀態管理的規則

React 組件內部的狀態是在渲染過程之間保持不變的封裝數據。useState() 是 React hook,負責管理功能組件內部的狀態。

我喜歡 useState() ,它確實使狀態處理變得非常容易。但是我經常遇到類似的問題:

  • 我應該將組件的狀態劃分爲小狀態,還是保持複合狀態?
  • 如果狀態管理變得複雜,我應該從組件中提取它嗎?該怎麼做?
  • 如果 useState() 的用法是如此簡單,那麼什麼時候需要 useReducer()?
    本文介紹了 3 條簡單的規則,可以回答上述問題,並幫助你設計組件的狀態。

No.1 一個關注點

有效狀態管理的第一個規則是:

使狀態變量負責一個問題。

使狀態變量負責一個問題使其符合單一責任原則。

讓我們來看一個複合狀態的示例,即一種包含多個狀態值的狀態。


1const [state, setState] = useState({
2    on: true,
3    count: 0
4});
5
6state.on    // => true
7state.count // => 0

狀態由一個普通的 JavaScript 對象組成,該對象具有 on 和 count 屬性。

第一個屬性 state.on 包含一個布爾值,表示開關。同樣,`state.count 包含一個表示計數器的數字,例如,用戶單擊按鈕的次數。

然後,假設你要將計數器加1:


1// Updating compound state
2setUser({
3    ...state,
4    count: state.count + 1
5});

你必須將整個狀態放在一起,才能僅更新 count。這是爲了簡單地增加一個計數器而調用的一個大結構:這都是因爲狀態變量負責兩個方面:開關和計數器。

解決方案是將複合狀態分爲兩個原子狀態 on 和 count:


1const [on, setOnOff] = useState(true);
2const [count, setCount] = useState(0);

狀態變量 on 僅負責存儲開關狀態。同樣,count 變量僅負責計數器。

現在,讓我們嘗試更新計數器:


1setCount(count + 1);
2// or using a callback
3setCount(count => count + 1);

count 狀態僅負責計數,很容易推斷,也很容易更新和讀取。

不必擔心調用多個 useState() 爲每個關注點創建狀態變量。

但是請注意,如果你使用過多的 useState() 變量,則你的組件很有可能就違反了“單一職責原則”。只需將此類組件拆分爲較小的組件即可。

No.2 提取複雜的狀態邏輯

將複雜的狀態邏輯提取到自定義 hook 中。

在組件內保留複雜的狀態操作是否有意義?

答案來自基本面(通常會發生這種情況)。

創建 React hook 是爲了將組件與複雜狀態管理和副作用隔離開。因此,由於組件只應關注要渲染的元素和要附加的某些事件偵聽器,所以應該把複雜的狀態邏輯提取到自定義 hook 中。

考慮一個管理產品列表的組件。用戶可以添加新的產品名稱。約束是產品名稱必須是唯一的。

第一次嘗試是將產品名稱列表的設置程序直接保留在組件內部:


 1function ProductsList() {
 2    const [names, setNames] = useState([]);  
 3    const [newName, setNewName] = useState('');
 4
 5    const map = name => <div>{name}</div>;
 6
 7    const handleChange = event => setNewName(event.target.value);
 8    const handleAdd = () => {    
 9        const s = new Set([...names, newName]);    
10        setNames([...s]);  };
11    return (
12        <div className="products">
13            {names.map(map)}
14            <input type="text" onChange={handleChange} />
15            <button onClick={handleAdd}>Add</button>
16        </div>
17    );
18}
19

names 狀態變量保存產品名稱。單擊 Add 按鈕時,將調用 addNewProduct() 事件處理程序。

在 addNewProduct() 內部,用 Set 對象來保持產品名稱唯一。組件是否應該關注這個實現細節?不需要。

最好將複雜的狀態設置器邏輯隔離到一個自定義 hook 中。開始做吧。

新的自定義鉤子 useUnique() 可使每個項目保持唯一性:


1// useUnique.js
2export function useUnique(initial) {
3    const [items, setItems] = useState(initial);
4    const add = newItem => {
5        const uniqueItems = [...new Set([...items, newItem])];
6        setItems(uniqueItems);
7    };
8    return [items, add];
9};

將自定義狀態管理提取到一個 hook 中後,ProductsList 組件將變得更加輕巧:


 1import { useUnique } from './useUnique';
 2
 3function ProductsList() {
 4  const [names, add] = useUnique([]);  const [newName, setNewName] = useState('');
 5
 6  const map = name => <div>{name}</div>;
 7
 8  const handleChange = event => setNewName(e.target.value);
 9  const handleAdd = () => add(newName);
10  return (
11    <div className="products">
12      {names.map(map)}
13      <input type="text" onChange={handleChange} />
14      <button onClick={handleAdd}>Add</button>
15    </div>
16  );
17}
18

const [names, addName] = useUnique([]) 啓用自定義 hook。該組件不再被複雜的狀態管理所困擾。

如果你想在列表中添加新名稱,則只需調用 add('New Product Name') 即可。

最重要的是,將複雜的狀態管理提取到自定義 hooks 中的好處是:

  • 該組件不再包含狀態管理的詳細信息
  • 自定義 hook 可以重複使用
  • 自定義 hook 可輕鬆進行隔離測試

    No.3 提取多個狀態操作

將多個狀態操作提取到化簡器中。

繼續用 ProductsList 的例子,讓我們引入“delete”操作,該操作將從列表中刪除產品名稱。

現在,你必須爲 2 個操作編碼:添加和刪除產品。處理這些操作,就可以創建一個簡化器並使組件擺脫狀態管理邏輯。

同樣,此方法符合 hook 的思路:從組件中提取複雜的狀態管理。

以下是添加和刪除產品的 reducer 的一種實現:


 1function uniqueReducer(state, action) {
 2    switch (action.type) {
 3        case 'add':
 4            return [...new Set([...state, action.name])];
 5        case 'delete':
 6            return state.filter(name => name === action.name);
 7        default:
 8            throw new Error();
 9    }
10}

然後,可以通過調用 React 的 useReducer() hook 在產品列表中使用 uniqueReducer():


 1function ProductsList() {
 2    const [names, dispatch] = useReducer(uniqueReducer, []);
 3    const [newName, setNewName] = useState('');
 4
 5    const handleChange = event => setNewName(event.target.value);
 6
 7    const handleAdd = () => dispatch({ type: 'add', name: newName });
 8    const map = name => {
 9        const delete = () => dispatch({ type: 'delete', name });    
10        return (
11            <div>
12                {name}
13                <button onClick={delete}>Delete</button>
14            </div>
15        );
16    }
17
18    return (
19        <div className="products">
20            {names.map(map)}
21            <input type="text" onChange={handleChange} />
22            <button onClick={handleAdd}>Add</button>
23        </div>
24    );
25}
26

const [names, dispatch] = useReducer(uniqueReducer, []) 啓用 uniqueReducer。names 是保存產品名稱的狀態變量,而 dispatch 是使用操作對象調用的函數。

當單擊 Add 按鈕時,處理程序將調用 dispatch({ type: 'add', name: newName })。調度一個 add 動作使 reducer uniqueReducer 向狀態添加一個新的產品名稱。

以同樣的方式,當單擊 Delete 按鈕時,處理程序將調用 dispatch({ type: 'delete', name })。remove 操作將產品名稱從名稱狀態中刪除。

有趣的是,reducer 是命令模式的特例。

總結

狀態變量應只關注一個點。

如果狀態具有複雜的更新邏輯,則將該邏輯從組件提取到自定義 hook 中。

同樣,如果狀態需要多個操作,請用 reducer 合併這些操作。

無論你使用什麼規則,狀態都應該儘可能地簡單和分離。組件不應被狀態更新的細節所困擾:它們應該是自定義 hook 或化簡器的一部分。

這 3 個簡單的規則能夠使你的狀態邏輯易於理解、維護和測試。

原文鏈接

https://dmitripavlutin.com/react-state-management/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章