一个简化使用 redux 和 saga 的辅助库,解决使用 redux 多个文件维护成本过高的问题。
合并 reducer, saga 为单个文件,并且隐藏 actions 和 types,使用时只调用函数即可。
本项目是在 redux-saga 上做的封装,使用 immer 作为 immutable 方案,所以依赖以下几个包。
immer >= 3.2.0
redux >= 4.0.0
redux-saga >= 1.0.0
如果项目执行时出现 "regeneratorRuntime is not defined" 的错误,在webpack配置里增加 "@babel/ployfill" 或者在babelrc里增加 "@babel/plugin-transform-runtime"
{
"plugins": ["@babel/plugin-transform-runtime"]
}
yarn add redux-saga-create
import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import { StoreContext, useMappedState } from 'redux-react-hook'
import { define, createStore } = 'redux-saga-create'
// 定义一个 reducer 和 saga 混合的对象,type 和 action 会自动生成,不需要处理
const user = define('user', { status: 0 }, {
// 普通函数,reducer
setInfo: (state, info, status) => {
// state 用 immer 封装过,可以直接设置值
state.name = name
state.status = status
},
// generator 函数,saga
*login(name, pwd) => {
// $set 是一个封装的设置 state 的快捷方式
yield this.$set('status', 1)
const user = yield fetchUser(name, pwd)
// 这里可以直接调用 reducer 函数
yield this.setInfo(user, 2)
}
})
// 创建 store,返回 store 对象和 dispatchs 的集合
const { store, dispatchs } = createStore({ user })
// 这里用了 redux-react-hook,用 react-redux 也是一样的
const App = () => {
const user = useMappedState(state => state.user)
useEffect(() => {
// 调用时直接调用定义的函数,不需要关心type和action
if (user.status === 0) dispatchs.user.login('name', 'pwd')
}, [])
...
}
ReactDOM.render(
<StoreContext.Provider value={store}>
<App />
</StoreContext.Provider>,
document.getElementById('root')
)
- namespace -- reducer 名称,不能重复
- defaultState -- state 默认值
- funcs -- 函数集合
普通函数会作为 reducer 使用,用来处理 state
{
// reducer 函数用immer包装过,可以直接赋值
setInfo: (state, name, email) => {
state.name = name
state.email = email
},
// 有返回结果时,state 会被整个替换为返回结果
setInfo2: (state, name, email) => {
return { ...state, name, email }
}
}
// 第一个参数 state 会自动注入,调用时不要传
user.setInfo(name, email)
generator 函数会作为 redux-saga 函数使用
*login(name, pwd) => {
let user = this.$state.user.info
if (user) return
yield this.$set('logging', true)
user = yield fetchUser(name, pwd)
yield this.$put(state => {
// 这里可以直接对 state 赋值
state.info = user
})
yield this.$remove('logging')
}
为了方便使用,generator 函数的 this 注入了 funcs 定义的函数和 一组快捷方式
- this.$set(path, value) -- 根据 key 赋值,path 支持 'a.b[0].c' 这种路径,如果子对象不存在,会自动创建
- this.$put(func) -- 通过函数修改 state 值
- this.$remove(path) -- 删除指定 path 数据
- this.$state -- 当前 state 数据
- this.$findState(name) -- 获取其他 reducer 下的 state
如果要使用 takLeading,tkeLatest 这样的 effect,可以传入数组来处理,数组第一个参数为effect,第二个参数为 generator 函数
{
login: [
takeLeading,
function*(name, pwd) {
...
}
]
}
- reducers -- define 函数定义的对象集
- options -- 额外参数
createStore({ user }, {
dev: bool // 是否支持 redux-devtool
onError: func // 异常捕获
})