入职后公司用的技术栈仍是 react,但状态管理由我本来熟悉的 redux 进化成了在 redux 基础上封装而成的 rematch。用起来也着实方便很多,减小了不少样板代码的编写,同时也不用引入中间件来管理异步的 action 了,但在用起来的过程当中不免就会引发了我对 rematch 的一些小疑问:javascript
带着上面的这些疑问,正好北京的夏天着实让我白天毫无出去瞎逛的动力,只有晚上才能出去跑个步啥的,所以闲来无事,我便翻开了 rematch 的源码开始读了起来。java
打开 rematch 的文件目录,来到 src 文件夹下,能够看到其文件的主要层级以下:react
.src
├── plugins
│ ├── dispatch.ts // 用于处理 reducer action
│ ├── effects.ts // 用于处理 effect action
├── typings // 类型约束文件
├── utils // 工具函数
├── index.ts // 入口文件
├── pluginFactory.ts
├── redux.ts // 基础redux
├── rematch.ts // Rematch基类
复制代码
而后打开 index.ts 文件,rematch 的 index 文件很是精简,现版本只存在两个具备实际应用价值的函数:redux
// 为给定对象建立model,而后返回做为参数接收的对象
export function createModel<S = any, M extends R.ModelConfig<S> = any>(model: M) {
return model
}
let count = 0
export const init = (initConfig: R.InitConfig = {}): R.RematchStore => {
// 若是不指定 name,就将其迭代的次数做为 name
const name = initConfig.name || count.toString()
count += 1
// 配置的对象,在这里使用 mergeConfig 来合并
const config: R.Config = mergeConfig({ ...initConfig, name })
// 在这里会先将 config 的信息传入 Rematch 函数中,而后会被 init 函数会执行,而它的结果也在此被返回,也就是咱们新生成的 store
return new Rematch(config).init()
}
export default {
init,
}
复制代码
index 文件中最为核心的就是 init 函数了,它主要作了如下工做:数组
上面的 index 文件到了 new Rematch(config).init() 就截然而止了,虽然咱们知道他已经在此过程当中,完成了一个 store 的建立,但这个过程我却并不知晓,所以接下来就是要去翻阅 rematch.ts 文件,首先扫一眼这个文件的大概状况,为后面的阅读作个铺垫:bash
import pluginFactory from './pluginFactory'
import dispatchPlugin from './plugins/dispatch'
import effectsPlugin from './plugins/effects'
import createRedux from './redux'
import * as R from './typings'
import validate from './utils/validate'
const corePlugins: R.Plugin[] = [dispatchPlugin, effectsPlugin]
/** * Rematch class * * an instance of Rematch generated by "init" */
export default class Rematch {
protected config: R.Config
protected models: R.Model[]
private plugins: R.Plugin[] = []
private pluginFactory: R.PluginFactory
constructor(config: R.Config) {
}
public forEachPlugin(method: string, fn: (content: any) => void) {
}
public getModels(models: R.Models): R.Model[] {
}
public addModel(model: R.Model) {
}
public init() {
}
}
复制代码
首先来看 rematch.ts 的类的声明部分:app
export default class Rematch {
protected config: R.Config
protected models: R.Model[]
private plugins: R.Plugin[] = []
private pluginFactory: R.PluginFactory
constructor(config: R.Config) {
// 这里的 config 就是从 index.ts 里传入的 config
this.config = config
this.pluginFactory = pluginFactory(config)
// 遍历 corePlugins 以及 config 中的 plugins
// 对其中的每一个 plugins 经过 pluginFactor.create 生成 plugins 数组
for (const plugin of corePlugins.concat(this.config.plugins)) {
this.plugins.push(this.pluginFactory.create(plugin))
}
// preStore: middleware, model hooks
// 将 middleware 执行一遍,并将 middleware 添加到 this.config.redux.middlewares 这个数组中
this.forEachPlugin('middleware', (middleware) => {
this.config.redux.middlewares.push(middleware)
})
}
... ...
}
复制代码
经过上面的代码咱们能够发现,当咱们去实例化 Rematch 时,首先会去执行这里的构造函数。这个构造函数主要是为了处理 plugin,并对两类不一样的 plugin 分别进行处理:异步
接下来就是 rematch 中定义的三个方法了:async
public forEachPlugin(method: string, fn: (content: any) => void) {
for (const plugin of this.plugins) {
if (plugin[method]) {
fn(plugin[method])
}
}
}
public getModels(models: R.Models): R.Model[] {
return Object.keys(models).map((name: string) => ({
name,
...models[name],
reducers: models[name].reducers || {},
}))
}
public addModel(model: R.Model) {
validate([
[!model, 'model config is required'],
[typeof model.name !== 'string', 'model "name" [string] is required'],
[model.state === undefined, 'model "state" is required'],
])
// run plugin model subscriptions
this.forEachPlugin('onModel', (onModel) => onModel(model))
}
复制代码
从这三个方法的名字咱们就不难看出这是哪一个方法的具体做用了,这三个方法主要是为了协助上面的构造函数以及下面 init() ,所以在此就不一一赘述了。下面就来重头戏 init() :函数
public init() {
// collect all models
// 经过 getModels 获取全部的 models
this.models = this.getModels(this.config.models)
// 遍历全部的 models 执行 addModels
for (const model of this.models) {
this.addModel(model)
}
// create a redux store with initialState
// merge in additional extra reducers
// 这里就是更新 state 的 reducer 了,后面具体会有分析
const redux = createRedux.call(this, {
redux: this.config.redux,
models: this.models,
})
const rematchStore = {
name: this.config.name,
...redux.store,
// dynamic loading of models with `replaceReducer`
model: (model: R.Model) => {
this.addModel(model)
redux.mergeReducers(redux.createModelReducer(model))
redux.store.replaceReducer(redux.createRootReducer(this.config.redux.rootReducers))
redux.store.dispatch({ type: '@@redux/REPLACE '})
},
}
this.forEachPlugin('onStoreCreated', (onStoreCreated) => {
const returned = onStoreCreated(rematchStore)
// if onStoreCreated returns an object value
// merge its returned value onto the store
if (returned) {
Object.keys(returned || {}).forEach((key) => {
rematchStore[key] = returned[key]
})
}
})
return rematchStore
}
复制代码
init() 会先执行 getModels 从而获取全部的 models ,并返回给 this.model, 而后经过遍历 this.model,对其中的每一个 models 都执行 addModel ,而后就会去调用 forEachPlugin。这块的执行逻辑稍微有点深,但其实本质上就是为了让全部的 models 都这么执行一次:
plugin.onModel(model)
复制代码
同时这里也会根据 model 的不一样的状况,去执行两种plugin:dispatchPlugin 和 effectPlugin。
其中 dispatchPlugin 的 onModel 处理以下:
// dispatch.ts
onModel(model: R.Model) {
this.dispatch[model.name] = {}
if (!model.reducers) {
return
}
for (const reducerName of Object.keys(model.reducers)) {
this.validate([
[
!!reducerName.match(/\/.+\//),
`Invalid reducer name (${model.name}/${reducerName})`,
],
[
typeof model.reducers[reducerName] !== 'function',
`Invalid reducer (${model.name}/${reducerName}). Must be a function`,
],
])
// 根据 model Name 和 reducer Name 生成相应的 dispatch 函数
this.dispatch[model.name][reducerName] = this.createDispatcher.apply(
this,
[model.name, reducerName]
)
}
}
复制代码
咱们能够看到 onModel 函数会遍历全部的 reducer,而后生成相应的 dispatch 函数(如何实现后面讨论)
而 effectsPlugin 的 onModel 则是这样的:
onModel(model: R.Model): void {
if (!model.effects) {
return
}
const effects =
typeof model.effects === 'function'
? model.effects(this.dispatch)
: model.effects
for (const effectName of Object.keys(effects)) {
this.validate([
[
!!effectName.match(/\//),
`Invalid effect name (${model.name}/${effectName})`,
],
[
typeof effects[effectName] !== 'function',
`Invalid effect (${model.name}/${effectName}). Must be a function`,
],
])
this.effects[`${model.name}/${effectName}`] = effects[effectName].bind(
this.dispatch[model.name]
)
// add effect to dispatch
// is assuming dispatch is available already... that the dispatch plugin is in there
this.dispatch[model.name][effectName] = this.createDispatcher.apply(
this,
[model.name, effectName]
)
// tag effects so they can be differentiated from normal actions
this.dispatch[model.name][effectName].isEffect = true
}
},
复制代码
这二者的 onModel 其实都差很少,最大的却别就是 effectsPlugin 的 onModel 在最后标记了 isEffect 为 true 。而后咱们就能够来看 this.createDispatcher 到底作了什么:
/** * createDispatcher * * genereates an action creator for a given model & reducer * @param modelName string * @param reducerName string */
createDispatcher(modelName: string, reducerName: string) {
return async (payload?: any, meta?: any): Promise<any> => {
const action: R.Action = { type: `${modelName}/${reducerName}` }
if (typeof payload !== 'undefined') {
action.payload = payload
}
if (typeof meta !== 'undefined') {
action.meta = meta
}
return this.dispatch(action)
}
}
复制代码
createDispatcher 函数的做用注释里说的很清楚,为 model 和 reducer 生成相应的 action creator,其内部实现是返回一个 async 函数,内部有由 model name 以及 reducer name 组成的套路化的 action type,而后再返回 dispatch(action) 的执行结果。这个 dispatch 又会到哪去呢?让咱们将目光回到 rematch.ts 中的 init() 中去,我在上面的代码中有提到这么一段代码:
// 这里就是更新 state 的 reducer 了,后面具体会有分析
const redux = createRedux.call(this, {
redux: this.config.redux,
models: this.models,
})
复制代码
实际上咱们这段代码就至关于咱们在 redux 中的 reducer ,他会接收到 dispatch(action) 从而变更 state。它的详情代码在 redux 中:
import * as Redux from 'redux'
import * as R from './typings'
import isListener from './utils/isListener'
const composeEnhancersWithDevtools = (
devtoolOptions: R.DevtoolOptions = {}
): any => {
const { disabled, ...options } = devtoolOptions
/* istanbul ignore next */
return !disabled &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(options)
: Redux.compose
}
export default function({ redux, models, }: { redux: R.ConfigRedux, models: R.Model[], }) {
const combineReducers = redux.combineReducers || Redux.combineReducers
const createStore: Redux.StoreCreator = redux.createStore || Redux.createStore
const initialState: any =
typeof redux.initialState !== 'undefined' ? redux.initialState : {}
this.reducers = redux.reducers
// combine models to generate reducers
this.mergeReducers = (nextReducers: R.ModelReducers = {}) => {
// merge new reducers with existing reducers
// 将已经新的 reducers 和 存在的 reducer 合并
this.reducers = { ...this.reducers, ...nextReducers }
// 若是没有 reducers 就直接返回 state
if (!Object.keys(this.reducers).length) {
// no reducers, just return state
return (state: any) => state
}
// 执行合并操做
return combineReducers(this.reducers)
}
this.createModelReducer = (model: R.Model) => {
const modelBaseReducer = model.baseReducer
const modelReducers = {}
// 遍历 model.reducers ,为其中的每一个 reducer 创造一个命名空间,并将其赋值到 modelReducer 中去
for (const modelReducer of Object.keys(model.reducers || {})) {
const action = isListener(modelReducer)
? modelReducer
: `${model.name}/${modelReducer}`
modelReducers[action] = model.reducers[modelReducer]
}
const combinedReducer = (state: any = model.state, action: R.Action) => {
// handle effects
if (typeof modelReducers[action.type] === 'function') {
return modelReducers[action.type](state, action.payload, action.meta)
}
return state
}
this.reducers[model.name] = !modelBaseReducer
? combinedReducer
: (state: any, action: R.Action) =>
combinedReducer(modelBaseReducer(state, action), action)
}
// initialize model reducers
// 建立 model 的 reducer
for (const model of models) {
this.createModelReducer(model)
}
this.createRootReducer = (
rootReducers: R.RootReducers = {}
): Redux.Reducer<any, R.Action> => {
const mergedReducers: Redux.Reducer<any> = this.mergeReducers()
if (Object.keys(rootReducers).length) {
return (state, action) => {
const rootReducerAction = rootReducers[action.type]
if (rootReducers[action.type]) {
return mergedReducers(rootReducerAction(state, action), action)
}
return mergedReducers(state, action)
}
}
return mergedReducers
}
const rootReducer = this.createRootReducer(redux.rootReducers)
const middlewares = Redux.applyMiddleware(...redux.middlewares)
const enhancers = composeEnhancersWithDevtools(redux.devtoolOptions)(
...redux.enhancers,
middlewares
)
// 建立一个 redux store,并返回 this 对象
this.store = createStore(rootReducer, initialState, enhancers)
return this
}
复制代码
总的来讲,rematch 其实就是在 redux 的基础上进行了封装,将咱们原本在 redux 中要写的诸如action creator等诸多样本代码给予封装,只须要关心 model 的划分以及写出合理的 reducers 以及 effects 便可。所以简单回答上面我看源码时所带的那两个问题:
如何区分reducer action以及 effect action
rematch 在内部有两种 plugin,一种是 dispatchPlugin,一种是 effectPlugin,前者只会让 reducers 进入逻辑代码,后者只会让 effects 进入逻辑代码,而且会标记 isEffect = true
它是怎么封装 诸如action creator这些以往在 redux 中繁琐的“样本”代码的?
rematch 在内部的 createDispatch 函数内部会根据 model name 以及 reducer name 建立相应的 action.type,以及对应的 action.payload,而后 dispatch 到 reducer 中去便可,同时也在这个函数内部内置的 async 来支持异步的 action。
图片拍摄于 冈仁波齐 原文地址: