在业务或者UI交互稍微复杂一些的项目里,都离不开状态管理的问题。无论是从后台API请求的数据仍是页面的UI状态,都须要有一个"Store"帮咱们去作状态管理。一般在项目中,咱们会引入 Redux 去负责这样的职责。可是 Redux 要维护大量的模板代码,加上 Redux 经过 connect 这种高阶组件的方式注入 state 和 dispatch 的方式并不直观,增长了理解的复杂度。React从16.8开始,引入了 React Hook,配合 Context API,能够在项目中来解决全局状态管理的问题。react
咱们先来总体看一下经过 React Context 和 Hooks 进行状态管理的架构图。咱们把构成架构的每一个负责不一样职责的节点称为"功能单元",下面列出了架构图中涉及的全部功能单元和对应的职责:ios
React Context:负责全局状态管理,经过提供的 Context API,能够进行状态的读写。每一个 Context 都会生成 Context.Provider 和 Context.Consumergit
在架构图中,描述功能单元之间的交互的"S"和"D",分别表示 state 和 dispatch 方法,其中github
熟悉 Redux 的人并不会对 Action 和 Reducer,以及 dispatch 的概念陌生,咱们也能从上述职责描述中看出,无论是在 Redux 仍是 React Hooks 中,这几个功能单元的职责是相同的,React 引入这些功能单元,也是为了处理各类复杂状态逻辑的场景。这里,巧妙的使用了 Context API 进行全局状态管理,使用 Hooks 提供的 useReducer 来去处理组件状态的变化,既使得数据流向变得清晰、可预测,又避免 Redux 复杂的模板代码生成和数据流管理。接下来,咱们来经过一个例子看一下代码的具体实现。json
咱们以游客在页面查看评论列表为例,来使用 React Context + Hooks 实现评论数据的全局状态管理axios
咱们须要在 React App 中建立一个 context 对象来存储评论列表数据。基于架构咱们知道,React Context 经过 Context.Provider 来为消费组件提供存储的 context,因此这里咱们先建立一个 <CommentProvider />
来返回 Context.Provider 和 Context 对象自己。架构
// CommentProvider.js const CommentContext = createContext({}); const CommentProvider = ({ children }) => { const [state, dispatch] = useReducer(commentReducer, { comments: [] }); return ( <CommentContext.Provider value={{ state, dispatch }}> {children} </CommentContext.Provider> ); };
在 <CommentProvider/>
中,调用 useReducer Hook 来去注册 commentReducer(会在后面建立),将解构的 state 和 dispatch 函数经过 CommentContext.Provider 提供给子组件。异步
当前是将 state 和 dispatch 未通过加工直接提供给了 Provider,不少实现会在 Provider 中解构 state,只去传递 <CommentProvider/>
须要的 state,另外也会将组装好的 Actions 直接传递给消费组件。虽然这种方式更明确要传递的数据和方法,可是解构 state 和 建立 Actions 的逻辑因为封装在 Provider 中,致使很难测试。因此,这里仍是借鉴了 Redux 的方式,在独立的 Action Creator 去定义 Action,而后在组件中直接调用。async
构建好 <CommentProvider/>
之后,在 <App/>
中,即可以经过 <CommentProvider/>
来组装 <CommentList />
了。ide
// App.js const App = () => ( <CommentProvider> <CommentList /> </CommentProvider> );
接下来,咱们来看一下,如何在 <CommentList />
中去使用 Context.Provider 提供的 context 值。
// CommentList.js import { fetchComments } from '../commentAction'; function CommentList() { const { state, dispatch } = useContext(CommentContext); useEffect(() => { fetchComments(dispatch); }, []); return (<ul> {state.comments.map((comment) => ( <li key={comment.id}><p>{comment.body}</p><span>{comment.name}</span></li> ))} </ul>); }
熟悉 React 的话应该清楚,一般在 useEffect Hook 中去处理 API 请求,来获取评论列表数据。在未使用 Context 对数据进行管理时,在组件内会直接经过组件内 state 来存取列表数据,而后触发组件的从新渲染。这里不一样的是,咱们经过 useContext 获取了全局的 state 数据,而后在 useEffect Hook 中,调用了 fetchComments Action,并无直接在组件内调用 setState。咱们在架构图中跟踪 "S" 的变化,能够看到,调用 Action 后,Reducer 会对 state 进行更新,并将新的 state 值更新到 context 中。那咱们就来看一下,如何建立 Action 和 Reducer 来更新 state 中评论列表的值。
// commentAction.js const fetchComments = async (dispatch) => { const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1/comments'); dispatch({ type: 'SET_COMMENTS', payload: response.data }); }
当异步获取评论列表数据后,便经过传入的 dispatch 方法来去发出一个更新评论列表的 'SET_COMMENTS' 的 Action,commentReducer 在接收到这个 Action 之后,便会从
payload 中取出传递的评论列表数据,而后更新到 context 的全局状态中。
// commentReducer.js const commentReducer = (state, action) => { if (action.type === 'SET_COMMENTS') { return { ...state, comments: action.payload }; } return state; };
须要注意的是,和 Redux 强调的一致,reducer 是一个纯函数,只根据传入的 Action 和 旧的 state,返回一个新的 state 值。context 拿到新的 state 后,便会对旧的 state 进行替换。到此,一个完整的更新评论列表的数据流已经走完。
咱们借用 React 引入的 Context 来去进行状态管理,经过 useReducer Hook 来去更新 state 的变化,而且借鉴了 Redux 中 Action 的抽象,来去描述组件中发生的事件或者变化。这种方式对全局状态的读取和更新进行了隔离,使得数据在功能单元间的流向更加清晰、易维护。
在实际项目中,须要注意 Context.Provider 中的嵌套关系,须要在合适的父节点提供 context 的值。能够经过UI或业务组件和状态之间的业务关系来去梳理。
能够在 https://github.com/dujuanxian/react-context-with-hook-comments-demo 中查看代码的实现,其中除了查看评论列表的功能,还包含建立评论的功能。
更多文章,请查看个人博客 dujuan.in