100 行代码实现相似 dva 的 redux 简易封装

实现功能相似 dva、nuomi、rematch、mirror 的数据流管理方式

  • redux 原生写法实现功能须要写大量的 action、reducer 模板代码,对于复杂项目来讲模板代码太多,且 action 文件和 reducer 文件等不停切换,开发体验较差。你们通常都会封装简化。react

  • 好比 dva 的实现方式,引入了 effects,把 state、effects、action、reducer 放在一个 model 文件,简化了 action 和 reducer 的定义方式。相似的还有 rematch,mirror。git

  • 今年项目重构时,因路由缓存等缘由,没有采用现有 dva 等框架,简易封装实现了相似 dva 的 redux 数据流实现。github

简单封装实现下相似 api 和使用方式。redux

实现代码

  • 命名 zoo,由于 z 字母开头文件列表中在最底部,方便查找

直接上代码,实现 redux 主体功能封装api

import { createStore } from 'redux';
import { Provider } from 'react-redux';

// 为 effects 或 reducer 添加 namespace, 方便保存到全局
const addNamespace = (obj, name) => {
  const newObj = {};
  Object.keys(obj).forEach(item => {
    newObj[`${name}/${item}`] = obj[item];
  });
  return newObj;
};

class Zoo {
  constructor() {
    // 定义公共 state、store、effects 等
    this.state = {};
    this.models = {};
    this.reducers = {};
    this.effects = {};
    this.store = {};
  }

  // zoo 初始化方法,传入每一个模块的 model
  init(models) {
    Object.values(models).forEach(item => {
      // 遍历加载每一个 model
      this.model(item);
    });

    // 建立并返回全局 store
    return this.createStore();
  }

  // 加载模块 model 方法
  model(modelObj) {
    const { state, reducer, effects, namespace } = modelObj;
    // 全局保存 state
    this.state[namespace] = state;
    this.models[namespace] = modelObj;

    // 保存 reducer
    const newReducer = addNamespace(reducer, namespace);
    this.reducers[namespace] = newReducer;

    // 保存 effects
    this.effects[namespace] = effects;
  }

  createStore() {
    // 合并 reducer, 建立 reducer 函数
    const reducer = (state = this.state, action) => {
      let newState = state;

      const { type, payload } = action;
      // 获取每一个 action 的 namespace
      const [namespace, typeName] = type.split('/');

      // 根据 namespace 获取对应 model 中 state 和 reducer 函数对象
      const currentState = newState[namespace];
      const currentReducer = this.reducers[namespace];

      // 若是 action 对应 reducer 存在,则根据函数修改 state,不然直接返回原 state
      if (currentReducer && currentReducer[type] && currentState) {
        // 根据 reducer 函数修改当前 namespace 的 state
        newState[namespace] = currentReducer[type](payload, currentState);
        // 修改后的 state 必须是新的对象,这样才不会覆盖旧的 state,可使修改生效
        newState = { ...newState };
      }

      return newState;
    };

    // 调用 redux createStore 建立 store
    this.store = createStore(reducer);

    const { dispatch, getState } = this.store;

    /** * 给每一个 model 的 effects 对象添加全局 store 的 dispatch、getState 方法 * 用于在 effects 中调用 dispatch * 同时对 effects 中的方法名添加 namespace, 用于组件中 dispatch 时区分模块 */
    Object.keys(this.effects).forEach(namespace => {
      this.effects[namespace].dispatch = ({ type, payload }) =>
        // 修改 action type,添加 namespace
        dispatch({ type: `${namespace}/${type}`, payload });
      this.effects[namespace].getState = getState;
    });

    return this.store;
  }
}

export default new Zoo();
复制代码
  • connect 封装
import React from 'react';
import { connect } from 'react-redux';
import zoo from './zoo';

// effectsArr 可做为 effects 依赖注入使用
export default (mapState, mapDispatch = {}, effectsArr = []) => {
  return Component => {
    const { getState, dispatch } = zoo.store;

    // 修改组件中的 dispatch 默认先触发 effects 中对应方法,不存在时做为正常 action dispatch
    const myDispatch = ({ type, payload }) => {
      const [typeId, typeName] = type.split('/');
      const { effects } = zoo;

      if (effects[typeId] && effects[typeId][typeName]) {
        return effects[typeId][typeName](payload);
      }

      dispatch({ type, payload });
    };

    const NewComponent = props => {
      const { effects } = zoo;
      const effectsProps = {};
      // 组件中扩展加入 effects 对象,更方便调用 effects 中的方法
      effectsArr.forEach(item => {
        if (effects[item]) {
          effectsProps[`${item}Effects`] = effects[item];
          myDispatch[`${item}Effects`] = effects[item];
        }
      });

      return <Component {...props} dispatch={myDispatch} {...effectsProps} />; }; return connect(mapState, mapDispatch)(NewComponent); }; }; 复制代码

如上,封装后的 connect 扩展了不少功能,组件中得到的 dispatch 再也不仅仅触发 action,而是直接 调用 effects 中的方法,更方便反作用处理,同时增长了 effects 依赖注入的接口(相似 Mobx 中的 inject)。缓存

zoo 实现完成,zoo 建立的 store 和 redux 原生建立的 store 并无区别。框架

zoo 使用

  • index.js
import { Provider } from 'react-redux';

import zoo from './zoo';
import todoModel from './zooExample/Todo/model';
import zooModel from './zooExample/Zoo/model';
import ZooExample from './zooExample/index';

// 只须要传入各模块 model 便可
const zooStore = zoo.init({
  todoModel,
  zooModel
});

render(
  <Provider store={zooStore}> <ZooExample /> </Provider>,
  document.getElementById('root')
);
复制代码
  • model.js
export default {
  namespace: 'zoo',
  state: {
    list: []
  },
  effects: {
    setState(payload) {
      const state = this.getState();
      this.dispatch({ type: 'setState', payload: payload });
    },
    addAnimal(name) {
      const { list } = this.getState().zoo;
      this.setState({ list: [...list, name] });
    },
    async deleteOne() {
      const { list } = this.getState().zoo;
      const res = [...list];
      // 模拟异步请求操做
      setTimeout(() => {
        res.pop();

        this.setState({ list: res });
      }, 1000);
    }
  },
  reducer: {
    setState: (payload, state) => ({ ...state, ...payload })
  }
};
复制代码
  1. 功能组件 ZooExample.js
import React, { useState, useEffect } from 'react';
import { connect } from '../../zoo';

const TestTodo = ({ dispatch, list, zooEffects }) => {
  const [value, setValue] = useState('');

  useEffect(() => {
    dispatch({ type: 'zoo/getAnimal' });
  }, []);

  const onAdd = () => {
    dispatch({
      type: 'zoo/addAnimal',
      payload: value
    });
  };

  const onDelete = () => {
    zooEffects.deleteOne();
    // 或 dispatch.zooEffects.deleteOne();
  };

  return (
    <div> <input onChange={e => setValue(e.target.value)} /> <button onClick={onAdd}>add animal</button> <button onClick={onDelete}>delete animal</button> <br /> <ul> {list.map((item, i) => { return <li key={item + i}>{item}</li>; })} </ul> </div> ); }; export default connect( state => { return { list: state.zoo.list }; }, {}, // effects 注入 ['todo', 'zoo'] )(TestTodo); 复制代码

一个简易的 redux 封装就完成了,约 100 多行代码,不须要写 action,不须要写 switch case,相比 dva 异步请求简单,dispatch 功能强大,能够实现 3 种方法触发 effects 也能够很简洁。异步

nuomi 中 redux 的封装原理基本一致async

以上代码能够正常使用,但 connect 方法的封装存在 ref 穿透等细节问题ide

示例代码仓库 github.com/iblq/zoo

相关文章
相关标签/搜索