1. 程式人生 > >使用70行程式碼配合hooks重新實現react-redux

使用70行程式碼配合hooks重新實現react-redux

原由

react-hooks 是 react 官方新的編寫推薦,我們很容易在官方的 useReducer 鉤子上進行一層很簡單的封裝以達到和以往 react-redux \ redux-thunk \ redux-logger 類似的功能,並且大幅度簡化了宣告。

react-hooks 的更多資訊請閱讀 reactjs.org/hooks;

先看看原始碼

這70行程式碼就是全部, 客官可以先閱讀,或許後續的說明文件也就不需要閱讀了。

  • 簡易的實現了 react-redux, redux-thunk 和 redux-logger
  • 預設使用 reducer-in-action 的風格, 也可宣告傳統的 reducer 風格
import React from 'react';

function devLog(lastState, nextState, action, isDev) {
  if (isDev) {
    console.log(
      `%c|------- redux: ${action.type} -------|`,
      `background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;`,
    );
    console.log('|--last:', lastState);
    console.log('|--next:'
, nextState); } } function reducerInAction(state, action) { if (typeof action.reducer === 'function') { return action.reducer(state); } return state; } export default function createStore(params) { const { isDev, reducer, initialState, actions, middleware } = { isDev: false, reducer
: reducerInAction, initialState: {}, actions: {}, middleware: params.isDev ? [devLog] : undefined, ...params, }; const AppContext = React.createContext(); const store = { useContext: function() { return React.useContext(AppContext); }, actions, dispatch: undefined, state: initialState, initialState, }; let realReducer; if (middleware) { realReducer = function(lastState, action) { let nextState = reducer(lastState, action); for (let i = 0; i < middleware.length; i++) { const newState = middleware[i](lastState, nextState, action, isDev); if (newState) { nextState = newState; } } return nextState; }; } else { realReducer = reducer; } const Provider = props => { const [state, dispatch] = React.useReducer(realReducer, initialState); if (!store.dispatch) { store.dispatch = async function(action) { if (typeof action === 'function') { await action(dispatch, store.state); } else { dispatch(action); } }; } store.state = state; return <AppContext.Provider {...props} value={state} />; }; return { Provider, store }; } 複製程式碼

reducer-in-action 風格

reducer-in-action是一個reducer函式,這 6 行程式碼就是 reducer-in-action 的全部:

function reducerInAction(state, action) {
  if (typeof action.reducer === 'function') {
    return action.reducer(state);
  }
  return state;
}
複製程式碼

它把 reducer 給簡化了,放置到了每一個 action 中進行 reducer 的處理。我們再也不需要寫一堆 switch,再也不需要時刻關注 action 的 type 是否和 redcer 中的 type 一致。

reducer-in-action 配合 thunk 風格,可以非常簡單的編寫 redux,隨著專案的複雜,我們只需要編寫 action,會使得專案結構更清晰。

使用

安裝, 您甚至可以將上面那70行程式碼拷貝至專案中, 需要 react 版本 >= 16.7

yarn add react-hooks-redux
複製程式碼

我們用了不到 35 行程式碼就聲明瞭一個完整的 react-redux 的例子, 擁抱 hooks。

import React from 'react';
import ReactHookRedux from 'react-hooks-redux';

// 通過 ReactHookRedux 獲得 Provider 元件和一個 sotre 物件
const { Provider, store } = ReactHookRedux({
  isDev: true, // 列印日誌
  initialState: { name: 'dog', age: 0 },
});

function actionOfAdd() {
  return {
    type: 'add the count',
    reducer(state) {
      return { ...state, age: state.age + 1 }; // 每次需要返回一個新的 state
    },
  };
}

function Button() {
  function handleAdd() {
    store.dispatch(actionOfAdd()); //dispatch
  }
  return <button onClick={handleAdd}>add</button>;
}

function Page() {
  const state = store.useContext();
  return <div>{state.age} <Button/> </div>;
}

export default function App() {
  return <Provider><Page /></Provider>;
}
複製程式碼

middleware 的編寫

絕大部分情況,你不需要編寫middleware, 不過它也極其簡單。middleware 是一個一維陣列,陣列中每個物件都是一個函式, 傳入了引數並且如果返回的物件存在, 就會替換成 nextState 並且繼續執行下一個 middleware。

我們可以使用 middleware 進行列印日誌、編寫chrome外掛或者二次處理 state 等操作。

我們看看 middleware 的原始碼:

let nextState = reducer(lastState, action);
for (let i = 0; i < middleware.length; i++) {
  const newState = middleware[i](lastState, nextState, action, isDev);
  if (newState) {
    nextState = newState;
  }
}
return nextState;
複製程式碼

效能和注意的事項

效能(和實現上)上最大的區別是,react-hooks-redux 使用 useConnect 鉤子代替 connect 高階元件進行 dispatch的派發。

在傳統的 react-redux 中,如果一個元件被 connect 高階函式進行處理,那麼當 dispatch 時,這個元件相關的 mapStateToProps 函式就會被執行,並且返回新的 props 以啟用元件更新。

而在 hooks 風格中,當一個元件被聲明瞭 useContext() 時,context 相關聯的物件被變更了,這個元件會進行更新。

理論上效能和 react-redux 是一致的,由於 hooks 相對於 class 有著更少的宣告,所以應該會更快一些。

所以,我們有節制的使用 useContext 可以減少一一些元件被 dispatch 派發更新。

如果我們需要手動控制減少更新 可以參考 useMemo 鉤子的使用方式進行配合。

以上都是理論分析,由於此庫和此文件是一個深夜的產物,並沒有去做效能上的基準測試,所以有人如果願意非常歡迎幫忙做一些基準測試。

非同步action的例子

import React from 'react';
import ReactHookRedux, { reducerInAction, devLog } from 'react-hooks-redux';

// 通過 ReactHookRedux 獲得 Provider 元件和一個 sotre 物件
const { Provider, store } = ReactHookRedux({
  isDev: true, // default is false
  initialState: { count: 0, asyncCount: 0 }, // default is {}
  reducer: reducerInAction, // default is reducerInAction 所以可省略
  middleware: [devLog], // default is [devLog] 所以可省略
  actions: {}, // default is {} 所以可省略
});

// 模擬非同步操作
function timeOutAdd(a) {
  return new Promise(cb => setTimeout(() => cb(a + 1), 500));
}

const actions = {
  // 如果返回的是一個function,我們會把它當成類似 react-thunk 的處理方式,並且額外增加一個ownState的物件方便獲取state
  asyncAdd: () => async (dispatch, ownState) => {
    const asyncCount = await timeOutAdd(ownState.asyncCount);
    dispatch({
      type: 'asyncAdd',
      // if use reducerInAction, we can add reducer Function repeat reducer
      reducer(state) {
        return {
          ...state,
          asyncCount,
        };
      },
    });
  },
};

function Item() {
  const state = store.useContext();
  return <div>async-count: {state.asyncCount}</div>;
}

function Button() {
  async function handleAdd() {
    // 使用 async dispatch
    await store.dispatch(actions.asyncAdd());
  }
  return <button onClick={handleAdd}>add</button>;
}

export default function App() {
  return (
    <Provider>
      <Item />
      <Button />
    </Provider>
  );
}

複製程式碼

謝謝閱讀。