useReducer-基礎概念篇html
useReducer-配合useContext使用react
咱們在第一篇文章中介紹了JavaScript中的reducer以及他的一些特色,對reducer不熟悉的小夥伴能夠先看看第一篇。git
React Hook功能正式發佈以後,容許在function component中擁有state和反作用(useEffect)。官方提供了兩種state管理的hook:useState、useReducer。下面咱們會經過一系列Demo逐步說明如何使用useReducer管理state。github
咱們先看看登陸頁常規的使用useState
的實現方式:數組
function LoginPage() {
const [name, setName] = useState(''); // 用戶名
const [pwd, setPwd] = useState(''); // 密碼
const [isLoading, setIsLoading] = useState(false); // 是否展現loading,發送請求中
const [error, setError] = useState(''); // 錯誤信息
const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登陸
const login = (event) => {
event.preventDefault();
setError('');
setIsLoading(true);
login({ name, pwd })
.then(() => {
setIsLoggedIn(true);
setIsLoading(false);
})
.catch((error) => {
// 登陸失敗: 顯示錯誤信息、清空輸入框用戶名、密碼、清除loading標識
setError(error.message);
setName('');
setPwd('');
setIsLoading(false);
});
}
return (
// 返回頁面JSX Element
)
}
複製代碼
上面Demo咱們定義了5個state來描述頁面的狀態,在login函數中當登陸成功、失敗時進行了一系列複雜的state設置。能夠想象隨着需求愈來愈複雜更多的state加入到頁面,更多的setState分散在各處,很容易設置錯誤或者遺漏,維護這樣的老代碼更是一個噩夢。app
下面看看如何使用useReducer改造這段代碼,先簡單介紹下useReducer。函數
const [state, dispatch] = useReducer(reducer, initState);
複製代碼
useReducer接收兩個參數:post
第一個參數:reducer函數,沒錯就是咱們上一篇文章介紹的。第二個參數:初始化的state。返回值爲最新的state和dispatch函數(用來觸發reducer函數,計算對應的state)。按照官方的說法:對於複雜的state操做邏輯,嵌套的state的對象,推薦使用useReducer。測試
聽起來比較抽象,咱們先看一個簡單的例子:
// 官方 useReducer Demo
// 第一個參數:應用的初始化
const initialState = {count: 0};
// 第二個參數:state的reducer處理函數
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函數
const [state, dispatch] = useReducer(reducer, initialState);
return (
<> // useReducer會根據dispatch的action,返回最終的state,並觸發rerender Count: {state.count} // dispatch 用來接收一個 action參數「reducer中的action」,用來觸發reducer函數,更新最新的狀態 <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 (
// 返回頁面JSX Element
)
}
複製代碼
乍一看useReducer改造後的代碼反而更長了,但很明顯第二版有更好的可讀性,咱們也能更清晰的瞭解state的變化邏輯。
能夠看到login函數如今更清晰的表達了用戶的意圖,開始登陸login、登陸success、登陸error。LoginPage不須要關心如何處理這幾種行爲,那是loginReducer須要關心的,表現和業務分離。
另外一個好處是全部的state處理都集中到了一塊兒,使得咱們對state的變化更有掌控力,同時也更容易複用state邏輯變化代碼,好比在其餘函數中也須要觸發登陸error狀態,只須要dispatch({ type: 'error' })。
useReducer可讓咱們將what
和how
分開。好比點擊了登陸按鈕,咱們要作的就是發起登錄操做dispatch({ type: 'login' })
,點擊退出按鈕就發起退出操做dispatch({ type: 'logout' })
,全部和how
相關的代碼都在reducer中維護,組件中只須要思考What
,讓咱們的代碼能夠像用戶的行爲同樣,更加清晰。
除此以外還有一個好處,咱們在前文提過Reducer其實一個UI無關的純函數,useReducer的方案是的咱們更容易構建自動化測試用例。
最後咱們總結一下這篇文章的一些主要內容:使用reducer的場景
state
是一個數組或者對象state
變化很複雜,常常一個操做須要修改不少state這篇文章咱們介紹了使用useReducer,幫助咱們集中式的處理複雜的state管理。但若是咱們的頁面很複雜,拆分紅了多層多個組件,咱們若是在子組件觸發這些state變化呢,好比在LoginButton觸發登陸操做? 咱們將在下篇文章介紹如何處理複雜組件樹結構的reducer共享問題。
若是有小夥伴看過thinking-in-react可能會有疑問,官方不是推薦State應該有子組件本身維護麼,爲何還要集中式的處理?
其實咱們並非推薦全部的state放一塊兒,而是若是確實有不少state跨多個組件公用須要放到page級別維護,這時候能夠考慮使用useReducer。
PS:推薦兩篇React State管理的文章
thinking-in-react沒看過的小夥伴牆裂推薦必定要看一遍,寫的很是的好。
最後慣例,歡迎你們star咱們的人人貸大前端團隊博客,全部的文章還會同步更新到知乎專欄 和 掘金帳號,咱們每週都會分享幾篇高質量的大前端技術文章。若是你喜歡這篇文章,但願能動動小手給個贊。