redux 原生写法实现功能须要写大量的 action、reducer 模板代码,对于复杂项目来讲模板代码太多,且 action 文件和 reducer 文件等不停切换,开发体验较差。你们通常都会封装简化。react
好比 dva 的实现方式,引入了 effects,把 state、effects、action、reducer 放在一个 model 文件,简化了 action 和 reducer 的定义方式。相似的还有 rematch,mirror。git
今年项目重构时,因路由缓存等缘由,没有采用现有 dva 等框架,简易封装实现了相似 dva 的 redux 数据流实现。github
简单封装实现下相似 api 和使用方式。redux
直接上代码,实现 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();
复制代码
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 并无区别。框架
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')
);
复制代码
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 })
}
};
复制代码
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