这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
前言
对于熟悉 React
的同学应该不会对 Redux
陌生,只要不是很小的需求,基本都会需要用到状态管理,而 Rematch
就是一个 Redux
的替代品。
它在 React
的基础上解决了很多问题,比如模板代码过多,同一个操作对应的变量、处理方法分散在多个页面等。
首先来看看 Rematch
是如何使用的。
举个例子
import { init } from '@rematch/core'
import * as models from './models'
const count = {
state: 0,
reducers: {
increment(state, payload) {
return state + payload
}
},
effects: {
async incrementAsync(payload, rootState) {
await new Promise(resolve => setTimeout(resolve, 1000))
this.increment(payload)
}
}
}
const store = init({
models: {
count,
},
})
export default store
可以看到,在 Redux
中需要创建 action.js
、state.js
、reducer.js
,但是 Rematch
允许你把这三者结合到一起,这组单元被称为 model
。
值得一提的是,Rematch
也需要使用 Redux
,也可以理解为 Rematch
是在 Redux
上做的扩展。
另外,由于 Rematch
是用 Typescript 开发的,为了方便解释,下面把 Typescript 类型全部去掉了再说明。
下面来看看 Rematch
都做了什么工作。
init
上面的例子中只使用了 init
这个 API。
export const init = (initConfig) => {
const config = createConfig(initConfig || {})
return createRematchStore(config)
}
传入 init 的配置,会首先传入 createConfig,然后会调用 createRematchStore。
function createConfig(initConfig) {
const storeName = initConfig.name ?? `Rematch Store ${count}`
count += 1
const config = {
name: storeName,
models: initConfig.models || {},
plugins: initConfig.plugins || [],
redux: {
reducers: {},
rootReducers: {},
enhancers: [],
middlewares: [],
...initConfig.redux,
devtoolOptions: {
name: storeName,
...(initConfig.redux?.devtoolOptions ?? {}),
},
},
}
validateConfig(config)
config.plugins.forEach((plugin) => {
if (plugin.config) {
config.models = merge(config.models, plugin.config.models)
if (plugin.config.redux) {
config.redux.initialState = merge(
config.redux.initialState,
plugin.config.redux.initialState
)
// ...
}
validatePlugin(plugin)
}
})
return config
}
这里首先创建了一个自增的 storeName
,然后将传入的 initConfig
对象全部拆分到 config 中,后续所有的操作都会在这个对象上处理。
config.redux
后续会传入给 Redux
作为 Redux
的初始化参数,毕竟 Rematch
是基于 Redux
实现的。
然后分别执行 validateConfig
、validatePlugin
。 这两个 validate
函数都是相同的套路。
function validatePlugin() {
validate(() => [
[
!ifDefinedIsFunction(plugin.onStoreCreated),
'Plugin onStoreCreated must be a function',
],
[
!ifDefinedIsFunction(plugin.onModel),
'Plugin onModel must be a function',
],
// ...
])
}
function validate(runValidations) {
if (process.env.NODE_ENV !== 'production') {
const validations = runValidations()
const errors: string[] = []
validations.forEach((validation) => {
const isInvalid = validation[0]
const errorMessage = validation[1]
if (isInvalid) {
errors.push(errorMessage)
}
})
if (errors.length > 0) {
throw new Error(errors.join(', '))
}
}
}
可以看到,这里提取出 validate
函数,可以对传入的需验证数组,依次遍历并检查数组的第一项是否为 true,如果不为 true 则抛出数据第二项的错误提示。
然后 validateConfig
、validatePlugin
都可以使用这个 validate
方法来实现:
- 对多种异常参数情况进行校验
- 没有冗余的判断,更简洁、优雅
createRematchStore
通过上文对 initConfig
的格式化以及异常传入格式校验,下面进入正文。
function createRematchStore(config) {
const bag = createRematchBag(config)
bag.reduxConfig.middlewares.push(createEffectsMiddleware(bag))
bag.forEachPlugin('createMiddleware', (createMiddleware) => {
bag.reduxConfig.middlewares.push(createMiddleware(bag))
})
const reduxStore = createReduxStore(bag)
// ...
}
首先将格式化后的 config 传入 createRematchBag
。
function createRematchBag(config) {
return {
models: createNamesModels(config.models),
reduxConfig: config.redux,
forEachPlugin(method, fn) {
config.plugins.forEach(plugin => {
if (plugin[method]) {
fn(plugin[method])
}
})
}
}
}
createRematchBag
返回了一个包含 models
、reduxConfig
、forEachPlugin
的对象。
models
对传入的models
进行校验reduxConfig
后续传入 redux 的配置。这里只改了个命名forEachPlugin
相当于Array.prototype.find
,可以检索出 method plugin 并且传入第二个参数表示的回调函数。
然后执行了 createEffectsMiddleware
。在 Rematch
中 effects
类似使用了 Redux + redux-thunk 中的 会返回函数的 action
。
function createEffectsMiddleware(bag) {
return store => next => action => {
if (action.type in bag.effects) {
next(action)
return bag.effects[action.type](
action.payload,
store.getState(),
action.meta,
)
}
return next(action)
}
}
可以看到,这里的 createEffectsMiddleware
其实就是一个 effects 的 Redux 中间件
。
开始的三个函数参数,store、next、action 分别代表了:
- Redux 的 store
- Redux 的 dispatch
- Redux 的 action
先判断 action.type
是否命中 Rematch
中的任意一个 effects。
如果命中,首先先执行 next(action)
,即使命中了 Rematch
的 effects ,也会继续执行后续可能存在的 reducer。
然后会执行对应的 Rematch effect
,并传入三个参数,分别是 action.payload
、 store state
、以及 action.meta
。
关于action.meta
action.meta 是为了扩展 action 而添加的参数。
小结
本篇介绍了 Rematch
对传入 initConfig
进行格式化并通过 createEffectsMiddleware
实现了一个 effect 中间件。