瘋狂的技術宅 前端先鋒 前端
翻譯:瘋狂的技術宅
做者:Dmitri Pavlutin
來源:dmitripavlutin
正文共:2630 字
預計閱讀時間:8 分鐘react
React 組件內部的狀態是在渲染過程之間保持不變的封裝數據。useState() 是 React hook,負責管理功能組件內部的狀態。ide
我喜歡 useState() ,它確實使狀態處理變得很是容易。可是我常常遇到相似的問題:函數
有效狀態管理的第一個規則是:測試
使狀態變量負責一個問題。編碼
使狀態變量負責一個問題使其符合單一責任原則。spa
讓咱們來看一個複合狀態的示例,即一種包含多個狀態值的狀態。翻譯
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 包含一個表示計數器的數字,例如,用戶單擊按鈕的次數。code
而後,假設你要將計數器加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() 變量,則你的組件頗有可能就違反了「單一職責原則」。只需將此類組件拆分爲較小的組件便可。
將複雜的狀態邏輯提取到自定義 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 中的好處是:
將多個狀態操做提取到化簡器中。
繼續用 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 個簡單的規則可以使你的狀態邏輯易於理解、維護和測試。
原文連接