在業務或者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