入職後公司用的技術棧仍是 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。
圖片拍攝於 岡仁波齊 原文地址: