rematch常用插件介绍
插件系统
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常用插件介绍相关推荐
- Maven常用插件介绍及如何打一个瘦jar包
目录 零:说在前面 一:常用插件的介绍 二:常用插件的对比 三:打一个瘦jar包 3.1:背景 3.2:解决方案 四:插件应用举例 4.1:maven-jar-plugin 举例及部分说明 4.2:m ...
- PostCSS及其常用插件介绍
前几天,PostCSS 6.0 分布了. PostCSS 处理了很多你不必处理的乏味工作.它很巧妙的不同于预处理器,提供了可选的且更简洁的编程语言,来编译成 CSS,如 Sass.Less 与 Sty ...
- jmeter常用插件介绍
jmeter作为一个开源的接口性能测试工具,其本身的小巧和灵活性给了测试人员很大的帮助,但其本身作为一个开源工具,相比于一些商业工具(比如LoadRunner),在功能的全面性上就稍显不足. 这篇博客 ...
- jmeter(二十一)jmeter常用插件介绍
https://www.cnblogs.com/imyalost/p/7751981.html jmeter作为一个开源的接口性能测试工具,其本身的小巧和灵活性给了测试人员很大的帮助,但其本身作为一个 ...
- VS2010常用插件介绍之Javascript插件(一)
今天在写JS时,写到500多行时,感觉代码已经很难看了.想到C#代码都有折叠功能,是不是JS也有呢.在选项中找了一下,没有相关了的设置功能,于是就上网找.一找可就不得了,发现了好多好用的插件.都可以在 ...
- gulp前端自动化构建工具:常用插件介绍及使用
Gulp是基于Node.js的一个构建工具(自动任务运行器),开发者可以使用它构建自动化工作流程(前端集成开发环境).一些常见.重复的任务,例如:网页自动刷新.CSS预处理.代码检测.压缩图片.等 ...
- 【vscode】vscode常用插件介绍
1.Angular Snippets(angular片段) 这个扩展为 TypeScript 和 HTML 添加了 Angular 的代码片段. 2.Auto Close Tag(自动闭合标签) 自动 ...
- Zotero(超好用的文献管理软件)安装+坚果云同步配置教程+常用插件介绍(全面)
文章目录 1.Zotero下载及安装 2.浏览器插件安装及使用 3. 配置坚果云同步 4.PDF自动命名插件 5. Zotero笔记功能介绍 6. 文献引用 zotero是开源的文献管理工具,可以方便 ...
- 我的 IDEA 常用插件介绍
本文同步发表于我的微信公众号,在微信搜索 及格 即可关注 这篇文章介绍一下我 IDEA 里安装的插件. 我的 IDEA 版本是IntelliJ IDEA 2021.3.3,并且打上了官方的汉化包,但我 ...
最新文章
- linux编码 form表单,Linux curl 模拟form表单提交信息和文件
- Git笔记(8) 远程仓库的使用
- views 多个文件夹 netcore_优化 .net core 应用的 dockerfile
- CRM管理系统、教育后台、赠品管理、优惠管理、预约管理、试听课、教师、学生、客户、学员、商品管理、科目、优惠券、完课回访、客户管理系统、收费、退费、回访、账号权限、订单流水、审批、转账、rp原型
- 12.12 带触发器按钮的输入框
- 图像处理常见算法(C++/OpenCV)
- 处理接口超时_开发中那些事儿:为啥update会超时呢?
- java高级多线程编程--关于线程的停止问题
- MyBatis插件开发:简单分页插件
- Wowza Media Server 入门系列--Wowza Media Server 安装及演示
- visio2016 两线相交去圆弧
- 发现同构:Gartner曲线、达克效应 与 跨越鸿沟
- 关于FL Studio ASIO驱动不工作的一个解决方案
- 西部数据移动硬盘真伪测试软件,我的西数硬盘是真的吗?网购西数移动硬盘辨别真伪的方法...
- vue mint swper
- PDF文件如何快速转换成Word文件?两个方法教你搞定
- 方向导数(Directional derivatives)
- 视频号最新组合玩法,打造全新变现渠道丨国仁网络
- 云计算厂商决战2020:虽分高下,但不决生死
- 生活随记 - 关于一万六/月的房子出租
热门文章
- 网站服务器域名费用入什么科目,域名费用计入什么科目
- 编写程序,从键盘输入任意一个字符,输出该字符是英文字母(不区分大小写)、数字字符还是其它字符。
- FFMPEG实现对AAC解码(采用封装格式实现)
- 抓包工具:教你搞懂websocket如何来分析
- mysql 创建唯一约束表
- Python爬虫采集抓取:Python3.x+Fiddler 采集抓取 APP 数据
- java this() super()_Java this()和super()的使用注意
- 读书笔记—《20岁的生活方式,决定30岁的打开方式》小令君
- cognos学习笔记
- php随机壁纸api,【编码书生】Bing 随机壁纸 API