我們?cè)诘谝黄恼轮薪榻B了JavaScript中的reducer以及他的一些特點(diǎn)缚甩,對(duì)reducer不熟悉的小伙伴可以先看看第一篇七芭。
React Hook功能正式發(fā)布之后轴或,允許在function component中擁有state和副作用(useEffect)。官方提供了兩種state管理的hook:useState诚镰、useReducer。下面我們會(huì)通過一系列Demo逐步說明如何使用useReducer管理state戴已。
useState版login
我們先看看登錄頁(yè)常規(guī)的使用useState
的實(shí)現(xiàn)方式:
function LoginPage() {
const [name, setName] = useState(''); // 用戶名
const [pwd, setPwd] = useState(''); // 密碼
const [isLoading, setIsLoading] = useState(false); // 是否展示loading埋凯,發(fā)送請(qǐng)求中
const [error, setError] = useState(''); // 錯(cuò)誤信息
const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登錄
const login = (event) => {
event.preventDefault();
setError('');
setIsLoading(true);
login({ name, pwd })
.then(() => {
setIsLoggedIn(true);
setIsLoading(false);
})
.catch((error) => {
// 登錄失敗: 顯示錯(cuò)誤信息、清空輸入框用戶名辐董、密碼悴品、清除loading標(biāo)識(shí)
setError(error.message);
setName('');
setPwd('');
setIsLoading(false);
});
}
return (
// 返回頁(yè)面JSX Element
)
}
上面Demo我們定義了5個(gè)state來描述頁(yè)面的狀態(tài),在login函數(shù)中當(dāng)?shù)卿洺晒蚝妗⑹r(shí)進(jìn)行了一系列復(fù)雜的state設(shè)置苔严。可以想象隨著需求越來越復(fù)雜更多的state加入到頁(yè)面孤澎,更多的setState分散在各處届氢,很容易設(shè)置錯(cuò)誤或者遺漏,維護(hù)這樣的老代碼更是一個(gè)噩夢(mèng)覆旭。
useReducer版login
下面看看如何使用useReducer改造這段代碼退子,先簡(jiǎn)單介紹下useReducer。
const [state, dispatch] = useReducer(reducer, initState);
useReducer接收兩個(gè)參數(shù):
第一個(gè)參數(shù):reducer函數(shù)姐扮,沒錯(cuò)就是我們上一篇文章介紹的絮供。第二個(gè)參數(shù):初始化的state。返回值為最新的state和dispatch函數(shù)(用來觸發(fā)reducer函數(shù)茶敏,計(jì)算對(duì)應(yīng)的state)。按照官方的說法:對(duì)于復(fù)雜的state操作邏輯缚俏,嵌套的state的對(duì)象惊搏,推薦使用useReducer贮乳。
聽起來比較抽象,我們先看一個(gè)簡(jiǎn)單的例子:
// 官方 useReducer Demo
// 第一個(gè)參數(shù):應(yīng)用的初始化
const initialState = {count: 0};
// 第二個(gè)參數(shù):state的reducer處理函數(shù)
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
// 返回值:最新的state和dispatch函數(shù)
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
// useReducer會(huì)根據(jù)dispatch的action恬惯,返回最終的state向拆,并觸發(fā)rerender
Count: {state.count}
// dispatch 用來接收一個(gè) action參數(shù)「reducer中的action」,用來觸發(fā)reducer函數(shù)酪耳,更新最新的狀態(tài)
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
了解了useReducer基本使用方法后浓恳,看看如何使用useReducer改造上面的login demo:
const initState = {
name: '',
pwd: '',
isLoading: false,
error: '',
isLoggedIn: false,
}
function loginReducer(state, action) {
switch(action.type) {
case 'login':
return {
...state,
isLoading: true,
error: '',
}
case 'success':
return {
...state,
isLoggedIn: true,
isLoading: false,
}
case 'error':
return {
...state,
error: action.payload.error,
name: '',
pwd: '',
isLoading: false,
}
default:
return state;
}
}
function LoginPage() {
const [state, dispatch] = useReducer(loginReducer, initState);
const { name, pwd, isLoading, error, isLoggedIn } = state;
const login = (event) => {
event.preventDefault();
dispatch({ type: 'login' });
login({ name, pwd })
.then(() => {
dispatch({ type: 'success' });
})
.catch((error) => {
dispatch({
type: 'error'
payload: { error: error.message }
});
});
}
return (
// 返回頁(yè)面JSX Element
)
}
乍一看useReducer改造后的代碼反而更長(zhǎng)了,但很明顯第二版有更好的可讀性碗暗,我們也能更清晰的了解state的變化邏輯颈将。
可以看到login函數(shù)現(xiàn)在更清晰的表達(dá)了用戶的意圖,開始登錄login言疗、登錄success晴圾、登錄error。LoginPage不需要關(guān)心如何處理這幾種行為噪奄,那是loginReducer需要關(guān)心的死姚,表現(xiàn)和業(yè)務(wù)分離。
另一個(gè)好處是所有的state處理都集中到了一起勤篮,使得我們對(duì)state的變化更有掌控力都毒,同時(shí)也更容易復(fù)用state邏輯變化代碼,比如在其他函數(shù)中也需要觸發(fā)登錄error狀態(tài)碰缔,只需要dispatch({ type: 'error' })账劲。
useReducer可以讓我們將what
和how
分開。比如點(diǎn)擊了登錄按鈕手负,我們要做的就是發(fā)起登陸操作dispatch({ type: 'login' })
涤垫,點(diǎn)擊退出按鈕就發(fā)起退出操作dispatch({ type: 'logout' })
,所有和how
相關(guān)的代碼都在reducer中維護(hù)竟终,組件中只需要思考What
蝠猬,讓我們的代碼可以像用戶的行為一樣,更加清晰统捶。
除此之外還有一個(gè)好處榆芦,我們?cè)谇拔奶徇^Reducer其實(shí)一個(gè)UI無關(guān)的純函數(shù),useReducer的方案是的我們更容易構(gòu)建自動(dòng)化測(cè)試用例喘鸟。
總結(jié)
最后我們總結(jié)一下這篇文章的一些主要內(nèi)容:使用reducer的場(chǎng)景
- 如果你的
state
是一個(gè)數(shù)組或者對(duì)象 - 如果你的
state
變化很復(fù)雜匆绣,經(jīng)常一個(gè)操作需要修改很多state - 如果你希望構(gòu)建自動(dòng)化測(cè)試用例來保證程序的穩(wěn)定性
- 如果你需要在深層子組件里面去修改一些狀態(tài)(關(guān)于這點(diǎn)我們下篇文章會(huì)詳細(xì)介紹)
- 如果你用應(yīng)用程序比較大,希望UI和業(yè)務(wù)能夠分開維護(hù)
這篇文章我們介紹了使用useReducer什黑,幫助我們集中式的處理復(fù)雜的state管理崎淳。但如果我們的頁(yè)面很復(fù)雜,拆分成了多層多個(gè)組件愕把,我們?nèi)绻谧咏M件觸發(fā)這些state變化呢拣凹,比如在LoginButton觸發(fā)登錄操作森爽? 我們將在下篇文章介紹如何處理復(fù)雜組件樹結(jié)構(gòu)的reducer共享問題。
如果有小伙伴看過thinking-in-react可能會(huì)有疑問嚣镜,官方不是推薦State應(yīng)該有子組件自己維護(hù)么爬迟,為什么還要集中式的處理?
其實(shí)我們并不是推薦所有的state放一起菊匿,而是如果確實(shí)有很多state跨多個(gè)組件公用需要放到page級(jí)別維護(hù)付呕,這時(shí)候可以考慮使用useReducer。
PS:推薦兩篇React State管理的文章
thinking-in-react沒看過的小伙伴墻裂推薦一定要看一遍跌捆,寫的非常的好徽职。
最后慣例,歡迎大家star我們的人人貸大前端團(tuán)隊(duì)博客疹蛉,所有的文章還會(huì)同步更新到知乎專欄 和 掘金賬號(hào)活箕,我們每周都會(huì)分享幾篇高質(zhì)量的大前端技術(shù)文章。如果你喜歡這篇文章可款,希望能動(dòng)動(dòng)小手給個(gè)贊育韩。
參考鏈接
- https://github.com/immerjs/immera
- https://reactjs.org/docs/context.html
- https://reactjs.org/docs/hooks-faq.html
- https://www.robinwieruch.de/react-usereducer-vs-usestate/
- https://www.robinwieruch.de/react-state-usereducer-usestate-usecontext/
- https://kentcdodds.com/blog/application-state-management-with-react