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咱们的人人贷大前端团队博客,全部的文章还会同步更新到知乎专栏 和 掘金帐号,咱们每周都会分享几篇高质量的大前端技术文章。若是你喜欢这篇文章,但愿能动动小手给个赞。