useReducer-基礎概念篇html
useReducer-配合useContext使用react
歡迎回到咱們的useReducer系列第三篇,若是這是你第一次看到這個系列,推薦先看看前兩篇:git
上篇文章結尾提到過使用useReducer,能夠幫助咱們集中式的處理複雜的state管理。但若是咱們的頁面很複雜,拆分紅了多層多個組件,咱們如何在子組件觸發這些state變化呢,好比在LoginButton觸發登陸失敗操做?github
這篇文章會介紹如何使用另一個高階Hook-useContext去解決這些問題。app
useContext
從名字上就能夠看出,它是以Hook的方式使用React Context。先簡單介紹Context
的概念和使用方式,更多Context的知識能夠參考官方文檔。ide
下面這段定義來自官方文檔:函數
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.
複製代碼
簡單來講Context
的做用就是對它所包含的組件樹提供全局共享數據的一種技術,talk is cheep 咱們直接看官方Demo:post
// 第一步:建立須要共享的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 組件並不須要透傳 ThemeContext
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton(props) {
// 第三步:使用共享 Context
const theme = useContext('ThemeContext');
render() {
return <Button theme={theme} />;
}
}
複製代碼
關於Context
還有一個比較重要的點是:當Context Provider的value發生變化是,他的全部子級消費者都會rerender。性能
看完上面Demo,咱們在回過頭思考如何利用context
去解決咱們問中開頭提到的子孫類組件出發reducer狀態變化。沒錯,就是將dispatch函數做爲context的value,共享給頁面的子組件。
// 定義初始化值
const initState = {
name: '',
pwd: '',
isLoading: false,
error: '',
isLoggedIn: false,
}
// 定義state[業務]處理邏輯 reducer函數
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函數
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,出發reducer操做state const dispatch = useContext(LoginContext); const click = () => { if (error) { // 子組件能夠直接 dispatch action dispatch({ type: 'error' payload: { error: error.message } }); } } } 複製代碼
能夠看到在useReducer結合useContext,經過context把dispatch函數提供給組件樹中的全部組件使用 ,而不用經過props添加回調函數的方式一層層傳遞。
使用Context相比回調函數的優點:
對比回調函數的自定義命名,Context的Api更加明確,咱們能夠更清晰的知道哪些組件使用了dispatch、應用中的數據流動和變化。這也是React一直以來單向數據流的優點。
更好的性能:若是使用回調函數做爲參數傳遞的話,由於每次render函數都會變化,也會致使子組件rerender。固然咱們能夠使用useCallback解決這個問題,但相比useCallback
React官方更推薦使用useReducer,由於React會保證dispatch始終是不變的,不會引發consumer組件的rerender。
更多信息能夠參考官方的FQA:
how-to-avoid-passing-callbacks-down
how-to-read-an-often-changing-value-from-usecallback
至此useReducer系列三篇就所有結束了,咱們簡單回顧一下:
state
很簡單,能夠直接使用useState
state
比較複雜(state是一個對象或者state很是多散落在各處)請使用userReducerstate
的變化,能夠考慮useReducer + useContext最後慣例,歡迎你們star咱們的人人貸大前端團隊博客,全部的文章還會同步更新到知乎專欄 和 掘金帳號,咱們每週都會分享幾篇高質量的大前端技術文章。若是你喜歡這篇文章,但願能動動小手給個贊。