關(guān)于React狀態(tài)管理的三個(gè)規(guī)則總結(jié)
前言
React 組件內(nèi)部的狀態(tài)是在渲染過(guò)程之間保持不變的封裝數(shù)據(jù)。useState() 是 React hook,負(fù)責(zé)管理功能組件內(nèi)部的狀態(tài)。
我喜歡 useState() ,它確實(shí)使?fàn)顟B(tài)處理變得非常容易。但是我經(jīng)常遇到類(lèi)似的問(wèn)題:
- 我應(yīng)該將組件的狀態(tài)劃分為小狀態(tài),還是保持復(fù)合狀態(tài)?
- 如果狀態(tài)管理變得復(fù)雜,我應(yīng)該從組件中提取它嗎?該怎么做?
- 如果 useState() 的用法是如此簡(jiǎn)單,那么什么時(shí)候需要 useReducer()?
本文介紹了 3 條簡(jiǎn)單的規(guī)則,可以回答上述問(wèn)題,并幫助你設(shè)計(jì)組件的狀態(tài)。
No.1 一個(gè)關(guān)注點(diǎn)
有效狀態(tài)管理的第一個(gè)規(guī)則是:
使?fàn)顟B(tài)變量負(fù)責(zé)一個(gè)問(wèn)題。
使?fàn)顟B(tài)變量負(fù)責(zé)一個(gè)問(wèn)題使其符合單一責(zé)任原則。
讓我們來(lái)看一個(gè)復(fù)合狀態(tài)的示例,即一種包含多個(gè)狀態(tài)值的狀態(tài)。
const [state, setState] = useState({
on: true,
count: 0
});
state.on // => true
state.count // => 0
狀態(tài)由一個(gè)普通的 JavaScript 對(duì)象組成,該對(duì)象具有 on 和 count 屬性。
第一個(gè)屬性 state.on 包含一個(gè)布爾值,表示開(kāi)關(guān)。同樣,``state.count` 包含一個(gè)表示計(jì)數(shù)器的數(shù)字,例如,用戶(hù)單擊按鈕的次數(shù)。
然后,假設(shè)你要將計(jì)數(shù)器加1:
// Updating compound state
setUser({
...state,
count: state.count + 1
});
你必須將整個(gè)狀態(tài)放在一起,才能僅更新 count。這是為了簡(jiǎn)單地增加一個(gè)計(jì)數(shù)器而調(diào)用的一個(gè)大結(jié)構(gòu):這都是因?yàn)闋顟B(tài)變量負(fù)責(zé)兩個(gè)方面:開(kāi)關(guān)和計(jì)數(shù)器。
解決方案是將復(fù)合狀態(tài)分為兩個(gè)原子狀態(tài) on 和 count:
const [on, setOnOff] = useState(true); const [count, setCount] = useState(0);
狀態(tài)變量 on 僅負(fù)責(zé)存儲(chǔ)開(kāi)關(guān)狀態(tài)。同樣,count 變量?jī)H負(fù)責(zé)計(jì)數(shù)器。
現(xiàn)在,讓我們嘗試更新計(jì)數(shù)器:
setCount(count + 1); // or using a callback setCount(count => count + 1);
count 狀態(tài)僅負(fù)責(zé)計(jì)數(shù),很容易推斷,也很容易更新和讀取。
不必?fù)?dān)心調(diào)用多個(gè) useState() 為每個(gè)關(guān)注點(diǎn)創(chuàng)建狀態(tài)變量。
但是請(qǐng)注意,如果你使用過(guò)多的 useState() 變量,則你的組件很有可能就違反了“單一職責(zé)原則”。只需將此類(lèi)組件拆分為較小的組件即可。
No.2 提取復(fù)雜的狀態(tài)邏輯
將復(fù)雜的狀態(tài)邏輯提取到自定義 hook 中。
在組件內(nèi)保留復(fù)雜的狀態(tài)操作是否有意義?
答案來(lái)自基本面(通常會(huì)發(fā)生這種情況)。
創(chuàng)建 React hook 是為了將組件與復(fù)雜狀態(tài)管理和副作用隔離開(kāi)。因此,由于組件只應(yīng)關(guān)注要渲染的元素和要附加的某些事件偵聽(tīng)器,所以應(yīng)該把復(fù)雜的狀態(tài)邏輯提取到自定義 hook 中。
考慮一個(gè)管理產(chǎn)品列表的組件。用戶(hù)可以添加新的產(chǎn)品名稱(chēng)。約束是產(chǎn)品名稱(chēng)必須是唯一的。
第一次嘗試是將產(chǎn)品名稱(chēng)列表的設(shè)置程序直接保留在組件內(nèi)部:
function ProductsList() {
const [names, setNames] = useState([]);
const [newName, setNewName] = useState('');
const map = name => <div>{name}</div>;
const handleChange = event => setNewName(event.target.value);
const handleAdd = () => {
const s = new Set([...names, newName]);
setNames([...s]); };
return (
<div className="products">
{names.map(map)}
<input type="text" onChange={handleChange} />
<button onClick={handleAdd}>Add</button>
</div>
);
}
names 狀態(tài)變量保存產(chǎn)品名稱(chēng)。單擊 Add 按鈕時(shí),將調(diào)用 addNewProduct() 事件處理程序。
在 addNewProduct() 內(nèi)部,用 Set 對(duì)象來(lái)保持產(chǎn)品名稱(chēng)唯一。組件是否應(yīng)該關(guān)注這個(gè)實(shí)現(xiàn)細(xì)節(jié)?不需要。
最好將復(fù)雜的狀態(tài)設(shè)置器邏輯隔離到一個(gè)自定義 hook 中。開(kāi)始做吧。
新的自定義鉤子 useUnique() 可使每個(gè)項(xiàng)目保持唯一性:
// useUnique.js
export function useUnique(initial) {
const [items, setItems] = useState(initial);
const add = newItem => {
const uniqueItems = [...new Set([...items, newItem])];
setItems(uniqueItems);
};
return [items, add];
};
將自定義狀態(tài)管理提取到一個(gè) hook 中后,ProductsList 組件將變得更加輕巧:
import { useUnique } from './useUnique';
function ProductsList() {
const [names, add] = useUnique([]); const [newName, setNewName] = useState('');
const map = name => <div>{name}</div>;
const handleChange = event => setNewName(e.target.value);
const handleAdd = () => add(newName);
return (
<div className="products">
{names.map(map)}
<input type="text" onChange={handleChange} />
<button onClick={handleAdd}>Add</button>
</div>
);
}
const [names, addName] = useUnique([]) 啟用自定義 hook。該組件不再被復(fù)雜的狀態(tài)管理所困擾。
如果你想在列表中添加新名稱(chēng),則只需調(diào)用 add('New Product Name') 即可。
最重要的是,將復(fù)雜的狀態(tài)管理提取到自定義 hooks 中的好處是:
- 該組件不再包含狀態(tài)管理的詳細(xì)信息
- 自定義 hook 可以重復(fù)使用
- 自定義 hook 可輕松進(jìn)行隔離測(cè)試
No.3 提取多個(gè)狀態(tài)操作
將多個(gè)狀態(tài)操作提取到化簡(jiǎn)器中。
繼續(xù)用 ProductsList 的例子,讓我們引入“delete”操作,該操作將從列表中刪除產(chǎn)品名稱(chēng)。
現(xiàn)在,你必須為 2 個(gè)操作編碼:添加和刪除產(chǎn)品。處理這些操作,就可以創(chuàng)建一個(gè)簡(jiǎn)化器并使組件擺脫狀態(tài)管理邏輯。
同樣,此方法符合 hook 的思路:從組件中提取復(fù)雜的狀態(tài)管理。
以下是添加和刪除產(chǎn)品的 reducer 的一種實(shí)現(xiàn):
function uniqueReducer(state, action) {
switch (action.type) {
case 'add':
return [...new Set([...state, action.name])];
case 'delete':
return state.filter(name => name === action.name);
default:
throw new Error();
}
}
然后,可以通過(guò)調(diào)用 React 的 useReducer() hook 在產(chǎn)品列表中使用 uniqueReducer():
function ProductsList() {
const [names, dispatch] = useReducer(uniqueReducer, []);
const [newName, setNewName] = useState('');
const handleChange = event => setNewName(event.target.value);
const handleAdd = () => dispatch({ type: 'add', name: newName });
const map = name => {
const delete = () => dispatch({ type: 'delete', name });
return (
<div>
{name}
<button onClick={delete}>Delete</button>
</div>
);
}
return (
<div className="products">
{names.map(map)}
<input type="text" onChange={handleChange} />
<button onClick={handleAdd}>Add</button>
</div>
);
}
const [names, dispatch] = useReducer(uniqueReducer, []) 啟用 uniqueReducer。names 是保存產(chǎn)品名稱(chēng)的狀態(tài)變量,而 dispatch 是使用操作對(duì)象調(diào)用的函數(shù)。
當(dāng)單擊 Add 按鈕時(shí),處理程序?qū)⒄{(diào)用 dispatch({ type: 'add', name: newName })。調(diào)度一個(gè) add 動(dòng)作使 reducer uniqueReducer 向狀態(tài)添加一個(gè)新的產(chǎn)品名稱(chēng)。
以同樣的方式,當(dāng)單擊 Delete 按鈕時(shí),處理程序?qū)⒄{(diào)用 dispatch({ type: 'delete', name })。remove 操作將產(chǎn)品名稱(chēng)從名稱(chēng)狀態(tài)中刪除。
有趣的是,reducer 是命令模式的特例。
總結(jié)
狀態(tài)變量應(yīng)只關(guān)注一個(gè)點(diǎn)。
如果狀態(tài)具有復(fù)雜的更新邏輯,則將該邏輯從組件提取到自定義 hook 中。
同樣,如果狀態(tài)需要多個(gè)操作,請(qǐng)用 reducer 合并這些操作。
無(wú)論你使用什么規(guī)則,狀態(tài)都應(yīng)該盡可能地簡(jiǎn)單和分離。組件不應(yīng)被狀態(tài)更新的細(xì)節(jié)所困擾:它們應(yīng)該是自定義 hook 或化簡(jiǎn)器的一部分。
這 3 個(gè)簡(jiǎn)單的規(guī)則能夠使你的狀態(tài)邏輯易于理解、維護(hù)和測(cè)試。
到此這篇關(guān)于React狀態(tài)管理的三個(gè)規(guī)則的文章就介紹到這了,更多相關(guān)React狀態(tài)管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Jenkins部署React項(xiàng)目的方法步驟
這篇文章主要介紹了使用Jenkins部署React項(xiàng)目的方法步驟,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03
React實(shí)現(xiàn)點(diǎn)擊刪除列表中對(duì)應(yīng)項(xiàng)
本文主要介紹了React 點(diǎn)擊刪除列表中對(duì)應(yīng)項(xiàng)的方法。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01
React useMemo與useCallabck有什么區(qū)別
useCallback和useMemo是一樣的東西,只是入?yún)⒂兴煌瑄seCallback緩存的是回調(diào)函數(shù),如果依賴(lài)項(xiàng)沒(méi)有更新,就會(huì)使用緩存的回調(diào)函數(shù);useMemo緩存的是回調(diào)函數(shù)的return,如果依賴(lài)項(xiàng)沒(méi)有更新,就會(huì)使用緩存的return2022-12-12
react-native組件中NavigatorIOS和ListView結(jié)合使用的方法
這篇文章主要給大家介紹了關(guān)于react-native組件中NavigatorIOS和ListView結(jié)合使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-09-09
教你應(yīng)用?SOLID?原則整理?React?代碼之單一原則
這篇文章主要介紹了如何應(yīng)用?SOLID?原則整理?React?代碼之單一原則,今天,我們將從一個(gè)糟糕的代碼示例開(kāi)始,應(yīng)用 SOLID 的第一個(gè)原則,看看它如何幫助我們編寫(xiě)小巧、漂亮、干凈的并明確責(zé)任的 React 組件,需要的朋友可以參考下2022-07-07
react-native 實(shí)現(xiàn)漸變色背景過(guò)程
這篇文章主要介紹了react-native 實(shí)現(xiàn)漸變色背景過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
解決React報(bào)錯(cuò)React?Hook?useEffect?has?a?missing?dependency
這篇文章主要為大家介紹了解決React報(bào)錯(cuò)React?Hook?useEffect?has?a?missing?dependency,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
react-redux的connect與React.forwardRef結(jié)合ref失效的解決
這篇文章主要介紹了react-redux的connect與React.forwardRef結(jié)合ref失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05

