歡迎回到我們的useReducer系列第三篇,如果這是你第一次看到這個(gè)系列蒲犬,推薦先看看前兩篇:
上篇文章結(jié)尾提到過使用useReducer鸦采,可以幫助我們集中式的處理復(fù)雜的state管理饱岸。但如果我們的頁(yè)面很復(fù)雜屡久,拆分成了多層多個(gè)組件看杭,我們?nèi)绾卧谧咏M件觸發(fā)這些state變化呢耍贾,比如在LoginButton觸發(fā)登錄失敗操作阅爽?
這篇文章會(huì)介紹如何使用另外一個(gè)高階Hook-useContext去解決這些問題。
useContext
從名字上就可以看出荐开,它是以Hook的方式使用React Context付翁。先簡(jiǎn)單介紹Context
的概念和使用方式,更多Context的知識(shí)可以參考官方文檔晃听。
context 介紹
下面這段定義來自官方文檔:
Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.
簡(jiǎn)單來說Context
的作用就是對(duì)它所包含的組件樹提供全局共享數(shù)據(jù)的一種技術(shù)百侧,talk is cheep 我們直接看官方Demo:
// 第一步:創(chuàng)建需要共享的context
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 第二步:使用 Provider 提供 ThemeContext 的值,Provider所包含的子樹都可以直接訪問ThemeContext的值
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// Toolbar 組件并不需要透?jìng)?ThemeContext
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton(props) {
// 第三步:使用共享 Context
const theme = useContext(ThemeContext);
render() {
return <Button theme={theme} />;
}
}
關(guān)于Context
還有一個(gè)比較重要的點(diǎn)是:當(dāng)Context Provider的value發(fā)生變化是能扒,他的所有子級(jí)消費(fèi)者都會(huì)rerender佣渴。
useContext版login
看完上面Demo,我們?cè)诨剡^頭思考如何利用context
去解決我們問中開頭提到的子孫類組件出發(fā)reducer狀態(tài)變化初斑。沒錯(cuò)辛润,就是將dispatch函數(shù)作為context的value,共享給頁(yè)面的子組件见秤。
// 定義初始化值
const initState = {
name: '',
pwd: '',
isLoading: false,
error: '',
isLoggedIn: false,
}
// 定義state[業(yè)務(wù)]處理邏輯 reducer函數(shù)
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;
}
}
// 定義 context函數(shù)
const LoginContext = React.createContext();
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 }
});
});
}
// 利用 context 共享dispatch
return (
<LoginContext.Provider value={{dispatch}}>
<...>
<LoginButton />
</LoginContext.Provider>
)
}
function LoginButton() {
// 子組件中直接通過context拿到dispatch砂竖,出發(fā)reducer操作state
const dispatch = useContext(LoginContext);
const click = () => {
if (error) {
// 子組件可以直接 dispatch action
dispatch({
type: 'error'
payload: { error: error.message }
});
}
}
}
可以看到在useReducer結(jié)合useContext真椿,通過context把dispatch函數(shù)提供給組件樹中的所有組件使用
,而不用通過props添加回調(diào)函數(shù)的方式一層層傳遞乎澄。
使用Context相比回調(diào)函數(shù)的優(yōu)勢(shì):
對(duì)比回調(diào)函數(shù)的自定義命名突硝,Context的Api更加明確,我們可以更清晰的知道哪些組件使用了dispatch三圆、應(yīng)用中的數(shù)據(jù)流動(dòng)和變化狞换。這也是React一直以來單向數(shù)據(jù)流的優(yōu)勢(shì)避咆。
更好的性能:如果使用回調(diào)函數(shù)作為參數(shù)傳遞的話舟肉,因?yàn)槊看蝦ender函數(shù)都會(huì)變化,也會(huì)導(dǎo)致子組件rerender查库。當(dāng)然我們可以使用useCallback解決這個(gè)問題路媚,但相比
useCallback
React官方更推薦使用useReducer,因?yàn)镽eact會(huì)保證dispatch始終是不變的樊销,不會(huì)引起consumer組件的rerender整慎。
更多信息可以參考官方的FQA:
how-to-avoid-passing-callbacks-down
how-to-read-an-often-changing-value-from-usecallback
總結(jié)
至此useReducer系列三篇就全部結(jié)束了,我們簡(jiǎn)單回顧一下:
- 如果你的頁(yè)面
state
很簡(jiǎn)單围苫,可以直接使用useState
- 如果你的頁(yè)面
state
比較復(fù)雜(state是一個(gè)對(duì)象或者state非常多散落在各處)請(qǐng)使用userReducer - 如果你的頁(yè)面組件層級(jí)比較深裤园,并且需要子組件觸發(fā)
state
的變化,可以考慮useReducer + useContext
最后慣例剂府,歡迎大家star我們的人人貸大前端團(tuán)隊(duì)博客拧揽,所有的文章還會(huì)同步更新到知乎專欄 和 掘金賬號(hào),我們每周都會(huì)分享幾篇高質(zhì)量的大前端技術(shù)文章腺占。如果你喜歡這篇文章淤袜,希望能動(dòng)動(dòng)小手給個(gè)贊。
參考資料
- https://github.com/immerjs/immer
- 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