React Hooks Redux

  • 2020.03-30

React Hooks 已经出来很长一段时间了,如何将Redux运用到Hooks中,这里推荐react-hooks-redux

yarn add react-hooks-redux

源码解析

简单版

import React from 'react';

function middlewareLog(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, middleware } = {
    isDev: false,
    reducer: reducerInAction,
    initialState: {},
    middleware: params.isDev ? [middlewareLog] : undefined,
    ...params,
  };
  const AppContext = React.createContext();
  const store = {
    isDev,
    _state: initialState,
    useContext: function() {
      return React.useContext(AppContext);
    },
    dispatch: undefined,
    getState: function() {
      return store._state;
    },
    initialState,
  };
  let isCheckedMiddleware = false;
  const middlewareReducer = function(lastState, action) {
    let nextState = reducer(lastState, action);
    if (!isCheckedMiddleware) {
      if (Object.prototype.toString.call(middleware) !== '[object Array]') {
        throw new Error("react-hooks-redux: middleware isn't Array");
      }
      isCheckedMiddleware = true;
    }
    for (let i = 0; i < middleware.length; i++) {
      const newState = middleware[i](store, lastState, nextState, action);
      if (newState) {
        nextState = newState;
      }
    }
    store._state = nextState;
    return nextState;
  };

  const Provider = props => {
    const [state, dispatch] = React.useReducer(middlewareReducer, initialState);
    if (!store.dispatch) {
      store.dispatch = async function(action) {
        if (typeof action === 'function') {
          await action(dispatch, store._state);
        } else {
          dispatch(action);
        }
      };
    }
    return <AppContext.Provider {...props} value={state} />;
  };
  return { Provider, store };
}

进阶版

import React from "react";

function reducerInAction(state, action) {
  if (typeof action.reducer === "function") {
    return action.reducer(state);
  }
  return state;
}

const subscribeCache = {};
let subscribeNum = 0;
function subscribe(fn) {
  if (typeof fn !== "function") {
    throw new Error("react-hooks-redux: subscribe params need a function");
  }
  subscribeNum++;
  subscribeCache[subscribeNum] = fn;
  function unSubscribe() {
    delete subscribeCache[subscribeNum];
  }
  return unSubscribe;
}

function runSubscribes(state, action) {
  for (const k in subscribeCache) {
    subscribeCache[k](state, action);
  }
}

const defalutOptions = {
  isDev: false,
  reducer: reducerInAction,
  initialState: {},
  middleware: [middlewareLog],
  autoSave: { item: undefined, keys: [] }
};

export default function createStore(options = defalutOptions) {
  const { isDev, reducer, initialState, middleware, autoSave } = {
    ...defalutOptions,
    ...options
  };
  const AppContext = React.createContext();
  const store = {
    isDev,
    _state: initialState,
    useContext: function() {
      return React.useContext(AppContext);
    },
    subscribe,
    dispatch: undefined,
    getState: function() {
      return store._state;
    },
    onload: [],
    initialState,
    connect: function(mapStateToProps, mapDispatchToProps) {
      return function(Comp) {
        return function(props) {
          const imm = store.useContext();
          const stateToProps = mapStateToProps(imm);
          const dispatchProps = mapDispatchToProps(store.dispatch, imm);
          const memoList = [];
          for (const k in stateToProps) {
            memoList.push(stateToProps[k]);
          }
          function render() {
            return <Comp {...stateToProps} {...dispatchProps} {...props} />;
          }
          return React.useMemo(render, memoList);
        };
      };
    }
  };
  let isCheckedMiddleware = false;
  const middlewareReducer = function(lastState, action) {
    if (!action) {
      return lastState;
    }
    let nextState = reducer(lastState, action);
    if (!isCheckedMiddleware) {
      if (Object.prototype.toString.call(middleware) !== "[object Array]") {
        throw new Error("react-hooks-redux: middleware isn't Array");
      }
      isCheckedMiddleware = true;
    }
    for (let i = 0; i < middleware.length; i++) {
      const newState = middleware[i](store, lastState, nextState, action);
      if (newState) {
        nextState = newState;
      }
    }
    store._state = nextState;
    runSubscribes(nextState, action);
    return nextState;
  };
  if (autoSave && autoSave.item) {
    autoSaveLocalStorage(store, autoSave.item, autoSave.keys);
  }
  function Provider(props) {
    const [state, dispatch] = React.useReducer(middlewareReducer, initialState);
    if (!store.dispatch) {
      store.dispatch = async function(action) {
        if (typeof action === "function") {
          await action(dispatch, store._state);
        } else {
          dispatch(action);
        }
      };
    }
    React.useEffect(() => {
      for (let i = 0; i < store.onload.length; i++) {
        store.onload[i]();
      }
    }, []);
    return <AppContext.Provider {...props} value={state} />;
  }
  return { Provider, store };
}

// 用于本地存储的方法
export const storage = {
  localName: "defaultIOKey",
  save: (v, theKey = storage.localName) => {
    const theType = Object.prototype.toString.call(v);
    if (theType === "[object Object]") {
      localStorage.setItem(theKey, JSON.stringify(v));
    } else if (theType === "[object String]") {
      localStorage.setItem(theKey, v);
    } else {
      console.warn("Warn: storage.save() param is no a Object");
    }
  },
  load: (theKey = storage.localName) => {
    try {
      const data = localStorage.getItem(theKey);
      if (data) {
        if (typeof data === "string") {
          return JSON.parse(data);
        }
        return data;
      }
    } catch (err) {
      console.warn("load last localSate error");
    }
  },
  clear: (theKey = storage.localName) => {
    localStorage.setItem(theKey, {});
  }
};

// 这里做自动保存的监听
export function autoSaveLocalStorage(store, localName, needSaveKeys) {
  if (localName) {
    storage.localName = localName;
  }
  if (Object.prototype.toString.call(needSaveKeys) !== "[object Array]") {
    // eslint-disable-next-line
    console.warn("autoSaveStorageKeys: params is no a Array");
  }
  //首次加载读取历史数据
  const lastLocalData = storage.load(storage.localName);
  if (Object.prototype.toString.call(lastLocalData) === "[object Object]") {
    store.onload.push(() => {
      store.dispatch({
        type: "localStorageLoad: IO",
        reducer: state => {
          // 如果是immutable 使用toJS
          if (state && state.toJS) {
            const data = {
              ...state.toJS(),
              ...lastLocalData
            };
            for (const key in data) {
              state = state.set(key, data[key]);
            }
            return state;
          }
          // 非immutable直接合并历史数据
          return {
            ...state,
            ...lastLocalData
          };
        }
      });
    });
  }
  // 只有needSaveKeys的修改会激发IO, lastDats保存之前的记录
  const lastDatas = {};
  needSaveKeys.forEach(v => {
    lastDatas[v] = undefined;
  });
  store.subscribe(() => {
    const state = store.getState();
    if (state && state.toJS) {
      //immutable 类型
      const nowDatas = {};
      let isNeedSave = false;
      needSaveKeys.forEach(v => {
        // 监听数据和 Immutable 配合做低开销校验
        if (Object.prototype.toString.call(v) === "[object Array]") {
          nowDatas[v] = state.getIn(v);
        } else {
          nowDatas[v] = state.get(v);
        }
        if (lastDatas[v] !== nowDatas[v]) {
          isNeedSave = true;
        }
        lastDatas[v] = nowDatas[v];
      });
      if (isNeedSave) {
        storage.save(nowDatas);
      }
    } else {
      // 非immutable做浅比较判断是否需要保存
      const nowDatas = {};
      let isNeedSave = true;
      needSaveKeys.forEach(v => {
        nowDatas[v] = state[v];
        if (lastDatas[v] !== nowDatas[v]) {
          isNeedSave = true;
        }
        lastDatas[v] = nowDatas[v];
      });
      if (isNeedSave) {
        storage.save(nowDatas);
        // needSaveKeys.forEach(v => {
        //   lastDatas[v] = nowDatas[v];
        // });
      }
    }
  });
}

export function middlewareLog(store, lastState, nextState, action) {
  if (store.isDev && !action.$NOLOG) {
    console.log(
      `%c|------- redux: ${action.type} -------|`,
      `background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;`
    );
    if (!action.$OBJLOG && nextState && typeof nextState.toJS === "function") {
      const next = {};
      nextState.map((d, k) => {
        next[k] = d;
      });
      if (action.$LASTLOG) {
        const last = {};
        lastState.map((d, k) => {
          last[k] = d;
        });
        console.log("|--last", last);
        console.log("|--next", next);
      } else {
        console.log("|--", next);
      }
    } else if (action.$LASTLOG) {
      const last = {};
      lastState.map((d, k) => {
        last[k] = d;
      });
      console.log("|--last", lastState);
      console.log("|--next", nextState);
    } else {
      console.log("|--", nextState);
    }
  }
}
上次更新时间: 2020-03-30 20:50:00