插件系统

rematch实现了一个插件系统,内置了dispatch和effects两个插件,分别用来增强dispatch和处理异步操作。rematch的插件,要符合rematch的要求,每个插件返回一个对象,这个对象可以包含几个属性,用来在不同的生命周期中对store进行操作。

对于每一个插件对象,提供了如下几个属性进行配置。

  • onInit:进行插件的初始化工作
  • config: 对rematch进行配置,会在rematch初始化之前,对插件的config配置进行merge操作
  • exposed:暴露给全局的属性和方法,可以理解为占位符,用于数据的共享
  • middleware:相当于redux的中间键,如果提供了这个属性,会把它交给redux进行处理
  • onModel:加载model的时候会调用的方法,用来对model进行处理
  • onStoreCreated:当rematch的store对象生成后,调用的方法,生成最终的store对象

DispatchPlugin

plugin包含exposed、onStoreCreated和onModel三个属性

const dispatchPlugin: R.Plugin = {exposed: {storeDispatch(action: R.Action, state: any) {console.warn('Warning: store not yet loaded')},storeGetState() {console.warn('Warning: store not yet loaded')},dispatch(action: R.Action) {return this.storeDispatch(action)},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)}},},// 在store创建完成的时候调用,将增强后的dispatch方法抛出onStoreCreated(store: any) {this.storeDispatch = store.dispatchthis.storeGetState = store.getStatereturn { dispatch: this.dispatch }},// 对model上的每个reducer创建action createor,并挂载到dispatch对象上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`,],])this.dispatch[model.name][reducerName] = this.createDispatcher.apply(this,[model.name, reducerName])}},
}
复制代码

这个插件用来处理model的reducers属性,如果model没有reducers,就直接退出;否则,遍历reducers,如果键名包含是/符号开头结尾的或者值不是一个函数,就报错退出。

通过createDispatcher函数,对每个reducer进行处理,包装成一个异步的action creator,action的type由model.name和reducerName组成。

onStoreCreated属性,会返回增强后的dispatch方法去重置redux默认的dispatch方法。

EffectsPlugin

effects plugin包含exposed、onModel和middleware三个属性。

const effectsPlugin: R.Plugin = {exposed: {effects: {},},// 将每个model上的effects添加到dispatch上,这样可以通过dispatch[modelName][effectName]来调用effect方法onModel(model: R.Model): void {if (!model.effects) {return}// model的effects可以是一个对象,或者是一个返回对象的函数,这个函数的参数是全局的dispatch方法const effects =typeof model.effects === 'function'? model.effects(this.dispatch): model.effectsfor (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])this.dispatch[model.name][effectName] = this.createDispatcher.apply(this,[model.name, effectName])// isEffect用来区分是普通的action,还是异步的,后面的loading插件就是通过这个字段来判断是不是异步操作this.dispatch[model.name][effectName].isEffect = true}},// 用来处理 async/await actions的redux中间键middleware(store) {return next => async (action: R.Action) => {if (action.type in this.effects) {await next(action)// 会把全局的state作为effect方法的第二个参数传入return this.effects[action.type](action.payload,store.getState(),action.meta)}return next(action)}},
}
复制代码

通过exposed将effects挂载到rematch对象上,用来保存所有model中的effects方法。

middleware属性用来定义中间键,处理async/await 异步函数。

onModel用来将所有model的effects方法存储在dispatch上,并使用'modelName/effectName'的key将对应的effect方法存储在全局的effects对象上。

@rematch/loading

对每个model中的effects自动添加loading状态。

export default (config: LoadingConfig = {}): Plugin => {validateConfig(config)const loadingModelName = config.name || 'loading'const converter =config.asNumber === true ? (cnt: number) => cnt : (cnt: number) => cnt > 0const loading: Model = {name: loadingModelName,reducers: {hide: createLoadingAction(converter, -1),show: createLoadingAction(converter, 1),},state: {...cntState,},}cntState.global = 0loading.state.global = converter(cntState.global)return {config: {// 增加一个loading model,用来管理所有的loading状态models: {loading,},},onModel({ name }: Model) {// 用户定义的model如果和注入的loadingmodel重名,则退出这个modelif (name === loadingModelName) {return}cntState.models[name] = 0loading.state.models[name] = converter(cntState.models[name])loading.state.effects[name] = {}const modelActions = this.dispatch[name]// 收集每个model上的effect方法Object.keys(modelActions).forEach((action: string) => {// 通过isEffect来判断是否是异步方法if (this.dispatch[name][action].isEffect !== true) {return}cntState.effects[name][action] = 0loading.state.effects[name][action] = converter(cntState.effects[name][action])const actionType = `${name}/${action}`// 忽略不在白名单中的actionif (config.whitelist && !config.whitelist.includes(actionType)) {return}// 忽略在黑名单中的actionif (config.blacklist && config.blacklist.includes(actionType)) {return}// 指向原来的effect方法const origEffect = this.dispatch[name][action]// 对每个effect方法进行包裹,在异步方法调用前后进行状态的处理const effectWrapper = async (...props) => {try {// 异步方法请求前,同步状态this.dispatch.loading.show({ name, action })const effectResult = await origEffect(...props)// 异步请求成功后,同步状态this.dispatch.loading.hide({ name, action })return effectResult} catch (error) {// 异步请求失败后,同步状态this.dispatch.loading.hide({ name, action })throw error}}// 使用包裹后的函数替代原来的effect方法this.dispatch[name][action] = effectWrapper})},}
}复制代码

这个插件会在store上增加一个名为loading的model,这个model的state有三个属性,分别为global、models和effects。

  • global:用来保存全局的状态,只要任意effect被触发,就会引起global的更新
  • models:它是一个对象,用来表示每个model的状态,只要是这个model下的effect被触发,就会引起对应model字段的更新
  • effects:它是一个对象,以model为单位,记录这个model下每个effects的状态,如果某个effect被触发,会去精确的更新这个effect对应的状态。

在默认情况下,会遍历所有model的effects,对每个isEffect为true的action,用一个异步函数进行包裹,使用try catch捕获错误。

常用的配置项

  • asNumber:默认是false,如果设置为true,那么状态就是异步被调用的次数
  • name:loading model的name,默认是loading
  • whitelist:白名单,一个 action 列表,只收集在列表中的action的状态。命名使用“model名称” / “action名称”,{ whitelist: ['count/addOne'] })
  • blacklist:黑名单一个 action 列表,不使用 loading 指示器。{ blacklist: ['count/addOne'] })
store.jsimport { init } from '@rematch/core'
import createLoadingPlugin from '@rematch/loading'// 初始化配置
const loading = createLoadingPlugin({})init({plugins: [loading]
})
复制代码
exmaple modle.jsconst asyncDelay = ms => new Promise(r => setTimeout(r, ms));
export default {state: 0,reducers: {addOne(s) {return s + 1}},effects: {async submit() {// mocking the delay of an effectawait asyncDelay(3000)this.addOne()},}
}复制代码
app.jsconst LoginButton = (props) => (<AwesomeLoadingButton onClick={props.submit} loading={props.loading}>Login</AwesomeLoadingButton>
)const mapState = state => ({count: state.example,loading: {global: state.loading.global,                   // true when ANY effect is runningmodel: state.loading.models.example,            // true when ANY effect on the `login` model is runningeffect: state.loading.effects.example.submit,   // true when the `login/submit` effect is running},
})const mapDispatch = (dispatch) => ({submit: () => dispatch.login.submit()
})export default connect(mapState, mapDispatch)(LoginButton)
复制代码

@rematch/immer

immer 是Mobx作者写的一个immutable库,利用ES6的proxy和defineProperty实现js的不可变数据。相比于ImmutableJS,操作更简单,也不用学习特有的API。

对于一个复杂的对象,immer会复用没有改变的部分,仅仅替换修改了的部分,相比于深拷贝,可以大大的减少开销。

对于react和redux,immer可以大大减少setState和reducer的代码量,并提供更好的可读性。

举个栗子,想要修改todoList中某项的状态,常规的写法

todos = [{todo: 'bbbb', done: true},{todo: 'aaa', done: false}
];state的情况
this.setState({todos: [...todos.slice(0, index),{...todos[index],done: !todos[index].done},...todos.slice(index + 1)]
})reducer的情况
const reducer = (state, action) => {switch (action.type) {case 'TRGGIER_TODO':const { members } = state;return {...state,todos: [...todos.slice(0, index),{...todos[index],done: !todos[index].done},...todos.slice(index + 1)]}default:return state}
}
复制代码

如果使用immer,代码可以简化为

import produce from 'immer'state的情况
this.setState(produce(draft => {draft.todos[index].done = !draft.todos[index].done;
))reducer的情况
const reducer = produce((draft, action) => {switch (action.type) {case 'TRGGIER_TODO':draft.todos[index].done = !draft.todos[index].done;}
})
复制代码

这个插件重写了redux的combineReducers方法,从而支持了immer。

import { Models, Plugin } from '@rematch/core'
import produce from 'immer'
import { combineReducers, ReducersMapObject } from 'redux'function combineReducersWithImmer(reducers: ReducersMapObject) {const reducersWithImmer = {}// model的reducer函数必须有返回,因为immer只支持对象类型for (const [key, reducerFn] of Object.entries(reducers)) {reducersWithImmer[key] = (state, payload) => {// 如果state不是对象,则直接返回reducer的计算值if (typeof state === 'object') {return produce(state, (draft: Models) => {const next = reducerFn(draft, payload)if (typeof next === 'object') {return next}})} else {return reducerFn(state, payload)}}}return combineReducers(reducersWithImmer)
}const immerPlugin = (): Plugin => ({config: {redux: {combineReducers: combineReducersWithImmer,},},
})export default immerPlugin
复制代码

使用插件

store.jsimport { init } from '@rematch/core'
import immerPlugin from '@rematch/immer';const immer = immerPlugin();init({plugins: [immer]
})复制代码
model.jsconst immerTodos = {state: [{todo: 'Learn typescript',done: true,}, {todo: 'Try immer',done: false,}],reducers: {addTodo(state, payload) {state.push(payload);return state;},triggerTodo(state, payload) {const { index } = payload;state[index].done = !state[index].done;return state;},},
};export default immerTodos;
复制代码

rematch-plugin-default-reducer

对于一般的model而言,reducer的作用就是根据state和action来生成新的state,类似于(state, action) => state,这个plugin的作用是为每个model生成一个名为setValues的reducer。如果是state是一个对象,会对action.payload和state使用Object.assign去进行属性的合并;如果state是基础类型,会使用action.payload去覆盖state。

const DefaultReducerPlugin = {onModel(model) {const { reducers = {} } = model;if (Object.keys(reducers).includes('setValues')) {return false;}reducers.setValues = (state, payload) => {if (isObject(payload) && isObject(state)) {return Object.assign({}, state, payload);}return payload;};this.dispatch[model.name].setValues = this.createDispatcher.apply(this,[model.name, 'setValues'],);},
};
复制代码

使用

const countModel = {state: {configList: [],},effects: {async fetchConfigList() {const res = await fetch({url});res && res.list && this.setValues({configList: res.list,});},},
};initimport defaultReducerPlugin from 'rematch-plugin-default-reducer';const store = init({...models,plugins: [defaultReducerPlugin],...});复制代码

rematch常用插件介绍相关推荐

  1. Maven常用插件介绍及如何打一个瘦jar包

    目录 零:说在前面 一:常用插件的介绍 二:常用插件的对比 三:打一个瘦jar包 3.1:背景 3.2:解决方案 四:插件应用举例 4.1:maven-jar-plugin 举例及部分说明 4.2:m ...

  2. PostCSS及其常用插件介绍

    前几天,PostCSS 6.0 分布了. PostCSS 处理了很多你不必处理的乏味工作.它很巧妙的不同于预处理器,提供了可选的且更简洁的编程语言,来编译成 CSS,如 Sass.Less 与 Sty ...

  3. jmeter常用插件介绍

    jmeter作为一个开源的接口性能测试工具,其本身的小巧和灵活性给了测试人员很大的帮助,但其本身作为一个开源工具,相比于一些商业工具(比如LoadRunner),在功能的全面性上就稍显不足. 这篇博客 ...

  4. jmeter(二十一)jmeter常用插件介绍

    https://www.cnblogs.com/imyalost/p/7751981.html jmeter作为一个开源的接口性能测试工具,其本身的小巧和灵活性给了测试人员很大的帮助,但其本身作为一个 ...

  5. VS2010常用插件介绍之Javascript插件(一)

    今天在写JS时,写到500多行时,感觉代码已经很难看了.想到C#代码都有折叠功能,是不是JS也有呢.在选项中找了一下,没有相关了的设置功能,于是就上网找.一找可就不得了,发现了好多好用的插件.都可以在 ...

  6. gulp前端自动化构建工具:常用插件介绍及使用

      Gulp是基于Node.js的一个构建工具(自动任务运行器),开发者可以使用它构建自动化工作流程(前端集成开发环境).一些常见.重复的任务,例如:网页自动刷新.CSS预处理.代码检测.压缩图片.等 ...

  7. 【vscode】vscode常用插件介绍

    1.Angular Snippets(angular片段) 这个扩展为 TypeScript 和 HTML 添加了 Angular 的代码片段. 2.Auto Close Tag(自动闭合标签) 自动 ...

  8. Zotero(超好用的文献管理软件)安装+坚果云同步配置教程+常用插件介绍(全面)

    文章目录 1.Zotero下载及安装 2.浏览器插件安装及使用 3. 配置坚果云同步 4.PDF自动命名插件 5. Zotero笔记功能介绍 6. 文献引用 zotero是开源的文献管理工具,可以方便 ...

  9. 我的 IDEA 常用插件介绍

    本文同步发表于我的微信公众号,在微信搜索 及格 即可关注 这篇文章介绍一下我 IDEA 里安装的插件. 我的 IDEA 版本是IntelliJ IDEA 2021.3.3,并且打上了官方的汉化包,但我 ...

最新文章

  1. linux编码 form表单,Linux curl 模拟form表单提交信息和文件
  2. Git笔记(8) 远程仓库的使用
  3. views 多个文件夹 netcore_优化 .net core 应用的 dockerfile
  4. CRM管理系统、教育后台、赠品管理、优惠管理、预约管理、试听课、教师、学生、客户、学员、商品管理、科目、优惠券、完课回访、客户管理系统、收费、退费、回访、账号权限、订单流水、审批、转账、rp原型
  5. 12.12 带触发器按钮的输入框
  6. 图像处理常见算法(C++/OpenCV)
  7. 处理接口超时_开发中那些事儿:为啥update会超时呢?
  8. java高级多线程编程--关于线程的停止问题
  9. MyBatis插件开发:简单分页插件
  10. Wowza Media Server 入门系列--Wowza Media Server 安装及演示
  11. visio2016 两线相交去圆弧
  12. 发现同构:Gartner曲线、达克效应 与 跨越鸿沟
  13. 关于FL Studio ASIO驱动不工作的一个解决方案
  14. 西部数据移动硬盘真伪测试软件,我的西数硬盘是真的吗?网购西数移动硬盘辨别真伪的方法...
  15. vue mint swper
  16. PDF文件如何快速转换成Word文件?两个方法教你搞定
  17. 方向导数(Directional derivatives)
  18. 视频号最新组合玩法,打造全新变现渠道丨国仁网络
  19. 云计算厂商决战2020:虽分高下,但不决生死
  20. 生活随记 - 关于一万六/月的房子出租

热门文章

  1. 网站服务器域名费用入什么科目,域名费用计入什么科目
  2. 编写程序,从键盘输入任意一个字符,输出该字符是英文字母(不区分大小写)、数字字符还是其它字符。
  3. FFMPEG实现对AAC解码(采用封装格式实现)
  4. 抓包工具:教你搞懂websocket如何来分析
  5. mysql 创建唯一约束表
  6. Python爬虫采集抓取:Python3.x+Fiddler 采集抓取 APP 数据
  7. java this() super()_Java this()和super()的使用注意
  8. 读书笔记—《20岁的生活方式,决定30岁的打开方式》小令君
  9. cognos学习笔记
  10. php随机壁纸api,【编码书生】Bing 随机壁纸 API