TL;DR
通过本文将了解:
- Rematch 的定位
- Rematch 创新点
- 对比 Rematch 和 dva
- Rematch 源码分析,了解其插件系统实现方式
Rematch 的定位
Rematch 的定位是基于 redux 的数据管理方案,并为开发者提供了更好的开发体验。更好的开发体验包括:
- 支持 TS
- 简化 redux 配置
- 减少模板代码
- 支持异步副作用处理机制
等等...
Rematch 创新点
1. 返回的 Store 支持 TS
使用 Rematch 后,我们可以通过 store.dispatch.model.xxx()
来执行一个 Action 操作,并且 store.dispatch
的类型是 TS 自动推算出来的,无需开发者手动定义。这不仅增强了编码的效率(类型自动推导)和正确性(类型检测),而且还解决了在纯 redux 方案中维护和使用 ActionType 的痛点。
由于每个 Model 是一个单独的文件,那么在真实的业务场景中,我们如何在一个 Model 中使用其他 Model 的数据或行为呢?要实现这样的功能,我们需要通过 Rematch 的最佳实践,分以下三步创建 Store。
- 将每个 Model 通过
createModel
方法创建出来 - 通过
Models<RootModel>
组装出最终的models
- 创建 Store
type CountState = number
// 每个 model 通过 createModel 方法创建出来
// increment1 方法中使用了 count2 中的 Action,其中 dispatch 是由 TS 自动推导出来的
const count1 = createModel<RootModel>()({
state: 0,
reducers: {},
effects: dispatch => {
return {
increment1: (payload: number) => {
dispatch.count2.increment2(payload)
},
}
},
})
// 每个 model 通过 createModel 方法创建出来
const count2 = createModel<RootModel>()({
state: 0,
reducers: {
increment2: (state: CountState, payload: number): CountState =>
state + payload,
},
})
// 通过 `Models<RootModel>` 组装出最终的 Model 类型
interface RootModel extends Models<RootModel> {
count1: typeof count1
count2: typeof count2
}
const models: RootModel = { count1, count2 }
const store = init({
models,
})
// store.getState() 和 store.dispatch 已经被自动推算出类型了
2. 基于 model 组织状态
在 Rematch 中,基于 Model 组织状态的好处有:
- 减少了模板代码。Model 组织状态后,可自动生成 ActionType。开发者便无需声明 ActionType,也不用在 reducer 函数中对 ActionType 进行判断了。
- 将状态和对状态的操作放在同一个文件中,简化了代码组织。无需纠结 reducer、ActionCreator 和 ActionType 分别应该放在哪里。
3. 副作用处理实现
粗略一看以为 Rematch 的副作用是基于 redux-thunk 实现的,但实际上并不是。Rematch 的副作用处理实现方式非常直观简单,可以通过以下两点讲清楚:
- effects 方法调用时,实际上会 dispatch 一个
{model}/{effect}
的 Action。 - Rematch 实现了一个 redux middleware,在该 middleware 中判断 Action 是否是一个 effect,如果是则调用 effect 函数。
既然副作用实现和 redux-thunk 没有任何关系,所以当 effects
是一个函数时,它并没有第二个参数 getState
,不要和 redux-thunk
搞混了。
// redux-thunk
function (dispatch, getState) {
// ...
}
// Rematch 的 effects
createModel()({
state: 0,
reducers: {},
// 这里没有第二个参数 getState
effects: dispatch => {
return {
increment1: (payload: number) => {
dispatch.count2.increment2(payload)
},
}
},
})
4. 提供更易用的 redux 使用方案
Rematch 的核心定位就是简化 redux 的使用,所以它内部支持了一些实用的功能。
- 提供
redux.rootReducers
选项,选项,rootReducers 优先于model.reducers
执行,rootReducers 的返回值会被作为新的 state 传入model.reducers
中,参考源码。可用于修改整个 Store 的数据,例如:重置状态。也可用于将纯 Redux 项目的 reducers 复制过来,便于平滑接入 Rematch。 - model 提供
baseReducer
选项,baseReducer
的优先级也比model.reducers
高,会将baseReducer
的返回值作为新的状态传给model.reducers
,参考源码。和redux.rootReducers
一样,也可用于重置状态。 - 提供
select
插件包,通过reselect
实现的计算属性。但个人觉得学习成本偏高,写起来有点看不懂。 - 提供
loading
插件,插件按照 effect/model/global 进行划分。 - 提供
persist
插件,可将 store 中数据持久化。 - 提供
immer
插件,可以在 reducer 中直接使用修改 state。 - 提供
updated
插件,可以记录每个 effect 的最后结束时间。 - 提供
typed-state
插件,用于每次 Action 执行完后,检查 State 是否还符合定义的类型。开发者通过model.typings
字段传入期望的类型。
对比 Rematch 和 dva
先谈下 Rematch 和 dva 的相同点:
- 通过 Model 来组织 State
- 简化 redux 的配置和使用
- 都提供了 immer 和 loading 插件
它们的不同点如下:
- dva 定位更偏向于基于 redux 的应用框架解决方案,它集成了 react-router、同构(服务端和浏览器端)请求方式,实现了懒加载 Model 和页面。而 Rematch 更倾向于提供更易用的 API,如:支持 TS、简化配置和多种插件,提升开发体验。
- dva 的副作用使用
redux-saga
实现,并且副作用是一个生成器函数,学习成本偏高。而 Rematch 的副作用实现机制更简单,更符合直觉。 - dva 支持 model 的动态新增和删除。但是 Rematch 只实现了新增 model,不支持删除 model,而且动态新增 model 后也不会自动修改 Store 的类型定义。可以认为 Rematch 并不推荐动态增删 model,因为动态修改后 Store 的类型就和运行时类型不一致了,Rematch 最大的优势(类型定义)就失效了。
插件系统
Rematch 的插件系统都是通过回调函数实现,Rematch 封装了 forEachPlugin
方法来调用插件的回调函数。
function forEachPlugin(method, fn): void {
config.plugins.forEach(plugin => {
if (plugin[method]) {
fn(plugin[method]!)
}
})
}
当看到 forEachPlugin
这种实现时,心中就产生了一个疑惑。这种方式如何使用插件函数的返回值呢?
参考下面的代码,通过修改闭包外部变量的方式,就可以将插件函数的返回值利用上了。
let rootReducer = {}
// 这种方式也可以利用插件的返回值
// 例如通过插件的 onRootReducer 钩子修改 rootReducer
bag.forEachPlugin("onRootReducer", onRootReducer => {
rootReducer = onRootReducer(rootReducer, bag) || rootReducer
})
看完 Rematch 源码后,感觉 Rematch 的源码还是比较整洁易读的,其插件系统实现得非常简单。
总结
通过本文我们知道 Rematch 是基于 Redux 实现的数据管理方案,其主要优势在于创建出的 Store 支持 TypeScript 类型定义。除了支持 TS 外,它还提供易用的副作用处理机制、分 Model 组织状态、多种插件支持等,极大地提升了开发体验。