70行代码配合hooks重新实现react-redux

2018-11-1021:53:37WEB前端开发Comments3,075 views字数 5597阅读模式

70行代码就是全部, 客官可以先阅读,或许后续的说明文档也就不需要阅读了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

  • 简易的实现了 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 的全部:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

function reducerInAction(state, action) {
  if (typeof action.reducer === 'function') {
    return action.reducer(state);
  }
  return state;
}
复制代码

它把 reducer 给简化了,放置到了每一个 action 中进行 reducer 的处理。我们再也不需要写一堆 switch,再也不需要时刻关注 action 的 type 是否和 redcer 中的 type 一致。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

reducer-in-action 配合 thunk 风格,可以非常简单的编写 redux,随着项目的复杂,我们只需要编写 action,会使得项目结构更清晰。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

使用

安装 react-hooks-redux, 需要 react 版本 >= 16.7文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

yarn add react-hooks-redux
复制代码

我们用了不到 35 行代码就声明了一个完整的 react-redux 的例子, 拥抱 hooks。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

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。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

我们可以使用 middleware 进行打印日志、编写chrome插件或者二次处理 state 等操作。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

我们看看 middleware 的源码:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

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 使用 useContext 钩子代替 connect 高阶组件进行 dispatch的派发。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

在传统的 react-redux 中,如果一个组件被 connect 高阶函数进行处理,那么当 dispatch 时,这个组件相关的 mapStateToProps 函数就会被执行,并且返回新的 props 以激活组件更新。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

而在 hooks 风格中,当一个组件被声明了 useContext() 时,context 相关联的对象被变更了,这个组件会进行更新。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

理论上性能和 react-redux 是一致的,由于 hooks 相对于 class 有着更少的声明,所以应该会更快一些。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

所以,我们有节制的使用 useContext 可以减少一些组件被 dispatch 派发更新。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

如果我们需要手动控制减少更新 可以参考 useMemo 钩子的使用方式进行配合。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

以上都是理论分析,由于此库和此文档是一个深夜的产物,并没有去做性能上的基准测试,所以有人如果愿意非常欢迎帮忙做一些基准测试。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

其他例子

异步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>
  );
}

复制代码

使用 immutableJS 配合 hooks 减少重渲染的例子

import React, { useCallback } from 'react';
import ReactHookRedux, { createDevLogFromImmutable } from 'react-hooks-redux';
import { Map } from 'immutable';

const { Provider, store } = ReactHookRedux({
  isDev: true, // 打印日志
  initialState: Map({ products: ['iPhone'] }),
  // createDevLogFromImmutable,提前声明getIn对象,可以有效的规避toJS的性能开销
  // 例如 createDevLogFromImmutable('user', ['data', 'local'], 'ui', 'products');
  middleware: [createDevLogFromImmutable('products')],
});

function actionAddProduct(product) {
  return {
    type: 'add the product',
    reducer(state) {
      return state.update('products', p => {
        p.push(product);
        return [...p];
      });
    },
  };
}

let num = 0;
function Button() {
  function handleAdd() {
    num += 1;
    store.dispatch(actionAddProduct('iPhone' + num)); //dispatch
  }
  return <button onClick={handleAdd}>add-product</button>;
}

function Page() {
  const state = store.useContext();
  // 从immutable获取对象,如果products未改变,会从堆中获取而不是重新生成新的数组
  const products = state.get('products');

  return useCallback(
    <div>
      <Button />
      {products.map(v => (
        <div>{v}</div>
      ))}
    </div>,
    [products], // 如果products未发生改变,不会进行进行重渲染
  );
}

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

复制代码

作者:超级大柱子
来源:掘金文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/7775.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/gcs/7775.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定