简单实现一个rematch

85 阅读2分钟

简单回顾一下官网rematch 使用

第一步:Init 初始化

init 用来配置你的 reducers, devtools & store。

import { init } from '@rematch/core'
import * as models from './models'

const store = init({
    models,
})

export default store

第二步:配置Models

该model促使state, reducers, async actions 和 action creators 放在同一个地方。是不是很方便

const delay = (time) => new Promise(resolve => setTimeout(() => resolve(), time));

// count model
export const count = {
  // 该model的初始state
  state: 0,
  // 存放纯函数,用于更新state
  reducers: {
    addBy(state, payload) {
      return state + payload
    },
    reduceBy(state, payload) {
      return state - payload
    }
  },
  // 副作用函数,可以异步处理action
  effects: (dispatch) => ({
    async addByAsync(payload, state) {
      await delay(1000)
      dispatch.count.addBy(1)
    },
    async reduceByAsync(payload, state) {
      await delay(1000)
      dispatch.count.reduceBy(1)
    }
  })
};

Step 3: Dispatch

dispatch 是我们如何在你的model中触发 reducers 和 effects。 Dispatch 标准化了你的action,而无需编写action types 或者 action creators。

import { dispatch } from '@rematch/core'
// state = { count: 0 }

// 调用方式, 这里需要注意如果使用rematch提供的loadPlugin插件,需要使用
// dispatch[model][action](payload)的方式
dispatch({ type: 'count/addBy', payload: 1 }) // state = { count: 1 }
dispatch.count.addBy(1) // state = { count: 2 }

Step 4: View

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider, connect } from 'react-redux'
import store from './index'

const Count = props => (
<div>
  <h1>数字: {props.count}</h1>
  <button onClick={props.addByOne}>增加1</button>
  <button onClick={props.addByOneAsync}>异步 增加1</button>
  <button onClick={props.reduceByOne}>减少1</button>
  <button onClick={props.reduceByOneAsync}>异步 减少1</button>
</div>
)

const mapState = state => ({
    count: state.count
})

const mapDispatch = ({ count: { addBy, addByAsync, reduceBy, reduceByAsync }}) => ({
  addByOne: () => addBy(1),
  addByOneAsync: () => addByAsync(1),
  reduceByOne: () => reduceBy(1),
  reduceByOneAsync: () => reduceByAsync(1)
});

const CountContainer = connect(mapState, mapDispatch)(Count)
ReactDOM.render(
    <Provider store={store}>
    <CountContainer />
    </Provider>,
document.getElementById('root')
)

从这里我们可以看到初始化入口为init 在源码中init 函数就是生成store的

import {createReduxStore, prepareModel, enhanceModel, createEffectsMiddleware} from './reduxStore'

function createRematchBag (config) {
  const models = config.models;
  const modelsBag = Object.keys(models).map(modelName => ({
    name: modelName,
    reducers: {},
    ...models[modelName]
  }))
  return {
    models:modelsBag, 
    reduxConfig: {
      reducers: {}, // 纯函数
      middlewares: [], // 中间件
    },
    effects: {},
  }
}
/**
 * 该函数被调用去设置Rematch。返回store。
 * @param {Object} config 
 * @returns 
 */
function createRematchStore(config) {
  // 存储重要的值
  const bag = createRematchBag(config)
  console.log('bag',bag)

  bag.reduxConfig.middlewares.push(createEffectsMiddleware(bag))
  // 将redux中createStore函数中的返回值
  const reduxStore = createReduxStore(bag)

  let rematchStore = {
    ...reduxStore,
    name: config.name
  }
  // 处理models文件中reducers事件,生成store.dispatch[modelName][actionName] 
  bag.models.forEach((model) => prepareModel(rematchStore, model))
  // 处理models文件中effects中的函数,生成store.dispatch[modelName][actionName]
	bag.models.forEach((model) => enhanceModel(rematchStore, bag, model))

  return rematchStore
}
/**
 *  入口函数
 *  @param {Object} initConfig 注册models {models: {[string]: model}}
 *  @returns {Object} 返回store 
 *  { @@observable: ƒ observable()
      dispatch: action => {…}
      getState: f getState()
      name: "Rematch Store 0"
      replaceReducer: ƒ replaceReducer(nextReducer)
      subscribe: ƒ subscribe(listener)}
 */
export const init = (initConfig) => {
  let count = 0
  const config = {
    name: initConfig.name ?? `Rematch Store ${count}`,
    models: initConfig.models || {},
  }
	return createRematchStore(config)
}

export default {
	init,
}

reduxStore.js

import * as Redux from 'redux'

/**
 * 
 * @param {Object} bag 
 * @param {Object} model
 */
export const createModelReducer = (bag, model) =>  {
  const modelReducers = {}
  const modelReducerKeys = Object.keys(model.reducers)
  modelReducerKeys.forEach((reducerKey) => {
    modelReducers[`${model.name}/${reducerKey}`] = model.reducers[reducerKey]
  })
  // 定义reducer
  const combinedReducer = (state = model.state, action) => {
    if (action.type in modelReducers) {
      return modelReducers[action.type](state, action.payload)
    }
    return state
  }
  // 生成命名空间:reducer对象
  bag.reduxConfig.reducers[model.name] = combinedReducer
}
/**
 * 调用Redux.createStore方法,
 * createStore(reducer, initState, compose(appleMiddleware(...middlewares))
 * @param {Object} bag 
 * @returns 
 */
export const createReduxStore = (bag) => {
  bag.models.forEach((model) => createModelReducer(bag, model))
  const middlewares = Redux.applyMiddleware(...bag.reduxConfig.middlewares)

  return Redux.createStore(Redux.combineReducers(
    bag.reduxConfig.reducers,
  ),{}
  ,middlewares
  )
} 
// 处理models中的reducers
export const prepareModel = (rematchStore, model) => {
  const modelDispatcher = {}
  rematchStore.dispatch[model.name] = modelDispatcher
  const modelReducersKeys = Object.keys(model.reducers)
	modelReducersKeys.forEach((reducerName) => {
		rematchStore.dispatch[model.name][reducerName] = createActionDispatcher(
			rematchStore,
			model.name,
			reducerName
		)
	})
}
// 处理models中的effects
export const enhanceModel = (rematchStore, bag,model) => {
  const modelDispatcher = rematchStore.dispatch[model.name] || {}
  let effects = {}
  if (model.effects) {
		effects =
			typeof model.effects === 'function'
				? (model.effects)(rematchStore.dispatch)
				: model.effects
	}

  const effectKeys = Object.keys(effects)
  effectKeys.forEach((effectName) => {

		bag.effects[`${model.name}/${effectName}`] =
			effects[effectName].bind(modelDispatcher)

		modelDispatcher[effectName] = createActionDispatcher(
			rematchStore,
			model.name,
			effectName,
		)
	})
}
// 这里将effects异步调用的逻辑放在中间件中处理
// 中间件结构 (store)=> (next) => (action)
export const createEffectsMiddleware = (bag) => {
  return (store) =>
		(next) =>
		(action) => {
			if (action.type in bag.effects) {
				next(action)
        // 调用
        // addByAsync(payload, state) {
        // },
				return (bag.effects)[action.type](
					action.payload,
					store.getState(),
				)
			}
			return next(action)
		}
}
const createActionDispatcher = (rematch,
  modelName,
  actionName, 
) => {
    return (payload) => {
        const action = { type: `${modelName}/${actionName}` }
  
        if (typeof payload !== 'undefined') {
          action.payload = payload
        }
        // 最终调用
        return rematch.dispatch(action)
      }
}

完整代码请看这里

这里可以看看rematch设计 重新思考redux