首發自個人github博客,歡迎starreact
注:如要運行本文的代碼,請先確認本身的react版本已支持hooksgit
react hooks出來已經有段時間了,本文不對hooks的具體用法做介紹,而是使用hooks實現一個簡易的基於context的reduxgithub
React hooks自帶了useReducer供咱們使用,它接受兩個參數,一是reducer函數,二是初始state,並返回state和dispatch函數,以下redux
const [state, dispatch] = useReducer(reducer, initialState);
這個函數本身實現的話也不難,以下:數組
const useMyReducer = (reducer, initialState) => { const [state, setState] = useState(initialState); const dispatch = action => { const newState = reducer(action, state); setState(newState); }; return [state, dispatch]; };
即將initialState做爲state的初始狀態傳入useState,dispatch則是一個函數,它會將接受的action和state傳給reducer,並獲取reducer的返回值賦給state異步
咱們先利用useReducer實現一個計數器的簡單頁面async
reducer函數和initialState以下:ide
const initialState = { count: 0 }; const reducer = (state, action) => { switch (action.type) { case "increase": return { ...state, count: state.count + 1 }; case "decrease": return { ...state, count: state.count - 1 }; default: return state; } };
計數器組件:函數
const Demo = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> counter:{state.count} <div> <button onClick={() => { dispatch({ type: "increase" }); }} > increase </button> <button onClick={() => { dispatch({ type: "decrease" }); }} > decrease </button> </div> </div> ); };
這就是第一版的redux了,但這個redux有些問題,就是它的state和dispatch是屬於本身的,其餘組件並不能拿到,也就是說,若是咱們的頁面有兩個Demo組件,它們的state是各自獨立,互不影響的測試
爲了解決上述問題,咱們必須擁有一個全局狀態,並將state和dispatch放入這個全局狀態中。這裏,咱們選用context做爲咱們的全局狀態,context在舊版React中不推薦使用,但在改進以後,官方開始推薦你們使用
咱們先建立一個context:
const context = React.createContext();
爲了各個組件都能拿到context的數據,咱們須要有一個Provider組件包在最外層:
const Provider = props => { const [state, dispatch] = useReducer(reducer, initialState); return ( <context.Provider value={{ state, dispatch }}> {props.children} </context.Provider> ); };
咱們將useReducer返回的state、dispatch傳入context.Provider中,讓它的children都能拿到
而後,咱們像下面同樣用Provider包在組件外層:
<Provider> <Demo /> <Demo /> </Provider>
咱們刪去計數器Demo組件中的:
const [state, dispatch] = useReducer(reducer, initialState);
加上經過useContext函數拿到context上的數據:
const { state, dispatch } = useContext(context);
要注意的是,傳入useContext函數的context必須是咱們以前經過React.createContext()
建立的context
這樣,即便是兩個Demo組件,它們也是共用一份數據了
很顯然,如今的context-redux和單純的redux同樣,只能dispatch一個對象,也就是說,這個dispatch操做是同步的,若是咱們要作異步的操做呢?很簡單,咱們借鑑redux-thunk的方法,讓dispatch能夠接受函數參數
改造Provider函數組件以下:
const Provider = props => { const [state, origin_dispatch] = useReducer(reducer, initialState); const dispatch = action => { if (typeof action === "function") { return action(origin_dispatch); } return origin_dispatch(action); }; return ( <context.Provider value={{ state, dispatch }}> {props.children} </context.Provider> ); };
咱們將userReducer函數返回的原始dispath命名爲origin_dispatch
,自定義dispatch函數,當action爲函數的時候,咱們執行action函數,並將origin_dispatch
看成參數傳進去;action不是函數,直接調用origin_dispatch
,不作處理
咱們測試一下:
const sleep = wait => { return new Promise(resolve => { setTimeout(() => resolve(), wait); }); }; const increaseCount = async dispatch => { await sleep(1000); dispatch({ type: "increase" }); };
<button onClick={() => { dispatch(increaseCount); }} > increase </button>
increaseCount
是一個異步函數,咱們將它看成參數傳入咱們封裝的新dispatch中,點擊increase按鈕,1s以後,計數器的數字加1,至此,咱們的context-redux也支持dispatch異步操做了
本文的代碼,我放在了本身的github上,這是傳送門