使用react hooks實現本身的context-redux

首發自個人github博客,歡迎starreact

注:如要運行本文的代碼,請先確認本身的react版本已支持hooksgit

react hooks出來已經有段時間了,本文不對hooks的具體用法做介紹,而是使用hooks實現一個簡易的基於context的reduxgithub

使用useReducer實現第一版redux

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中

爲了解決上述問題,咱們必須擁有一個全局狀態,並將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上,這是傳送門

相關文章
相關標籤/搜索