第一次写开源项目, redux 中间件,望老铁们给点建议 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
SophiaPeng
V2EX    开源软件

第一次写开源项目, redux 中间件,望老铁们给点建议

  •  
  •   SophiaPeng 2020-08-27 16:11:45 +08:00 1981 次点击
    这是一个创建于 1871 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    今天给大家带来一款 redux 中间件 : rc-redux-model, 提供一种较为舒适的数据状态管理书写方式,让你简洁优雅的去开发;内部自动生成 action,只需记住一个 action,可以修改任意的 state 值,方便简洁,从而释放你的 CV 键~

    源码仓库地址 : rc-redux-model ,如果你觉得不错,求个

    背景

    相信大家都了解 redux,并且也认同这种数据流的方式(毕竟不认同,你也不会用嘛~),然,世间万物,皆有利弊。

    本身我使用 redux 并不会有什么所谓的“痛点”,因为 redux 默认只支持同步操作,让使用者自行选择处理异步,对于异步请求 redux 是无能为力的。可以这么说,它保证自己是纯粹的,脏活累活都丢给别人去干。

    于是我的痛点在于 : 如何处理异步请求,为此我使用了 redux-saga 去解决异步的问题

    但是在使用 redux + redux-saga 中,我发现,这会让我的 [重复性] 工作变多(逐步晋升 CV 工程师),因为它在我们项目中,会存在嗦的样板代码。

    举个 : 异步请求,获取用户信息,我需要创建 sagas/user.jsreducers/user.jsactions/user.js,为了统一管理 const,我还会有一个 const/user.js,然后在这些文件之间来回切换。

    分文件应该是一种默认的规范吧?

    // const/user.js const FETCH_USER_INFO = 'FETCH_USER_INFO' const FETCH_USER_INFO_SUCCESS = 'FETCH_USER_INFO_SUCCESS' 
    // actions/user.js export function fetchUserInfo(params, callback) { return { type: FETCH_USER_INFO, params, callback, } } 
    // sagas/user.js function* fetchUserInfoSaga({ params, callback }) { const res = yield call(fetch.callAPI, { actionName: FETCH_USER_INFO, params, }) if (res.code === 0) { yield put({ type: FETCH_USER_INFO_SUCCESS, data: res.data, }) callback && callback() } else { throw res.msg } } 
    // reducers/user.js function userReducer(state, action) { switch (action.type) { case FETCH_USER_INFO_SUCCESS: return Immutable.set(state, 'userInfo', action.data) } } 

    没错, 这种样板代码,简直就是 CV 操作,只需要 copy 一份,修改一下名称,对我个人而言,这会让我不够专注,分散管理 const 、action 、saga 、reducer 一套流程,需要不断的跳跃思路。

    而且文件数量会变多,我是真的不喜欢如此繁琐的流程,有没有好的框架能帮我把这些事都做完呢?

    dva

    以下引用 dva 官网的介绍 :

    基于 redux 和 redux-saga 的数据流方案,让你在一个 model 文件中写所有的 action 、state 、effect 、reducers 等,然后为了简化开发体验,内置了 react-router 和 fetch.

    聊聊我对 dva 的看法,官方说了,基于 redux + redux-saga 的方案,只是在你写的时候,都写在一个 model 文件,然后它帮你做一些处理;其次它是一个框架,而不是一个库,是否意味着: 我在项目开始之前,我就需要确定项目的架构是不是用 dva,如果开发一半,我想换成 dva 这种状态管理的写法,而去引入 dva,是否不合理?

    再或者,我只是做一些 demo 、写点小型的个人项目,但我又想像写 dva 的数据状态管理 model 那种方式,引入 dva 是不是反而变得笨重呢?

    回过头来看,我的出发点是 : 在于解决繁琐重复的工作,store 文件分散,state 类型和赋值错误的问题,为此,对于跟我一样的用户,提供了一个写状态管理较为[舒服]的书写方式,大部分情况下兼容原先项目,只需要安装这个包,就能引入一套数据管理方案,写起来又舒服简洁,开心开心的撸代码,不香吗?

    于是 rc-redux-model 就这样出现了~

    rc-redux-model

    需要明确的是 : rc-redux-model 是一个中间件,提供一种更为简洁和方便的数据状态管理[书写方式]

    参考了 dva 的数据流方案,在一个 model 文件中写所有的 actionreducerstate,解读了 redux-thunk 的源码,内部实现了一个中间件,同时提供默认行为 action,调用此 action 可以直接修改任意值的 state,方便简洁,让你忍不住说 WC

    特性

    • 轻巧简洁,写数据管理就跟写 dva 一样舒服
    • 异步请求由用户自行处理,内部支持 call 方法,可调用提供的方法进行转发,该方法返回的是一个 Promise
    • 参考 redux-thunk,内部实现独立的中间件,所有的 action 都是异步 action
    • 提供默认行为 action,调用此 action,可以修改任意的 state 值,解决你重复性写 action 、reducers 问题
    • 内置 seamless-immutable ,只需开启配置,让你的数据不可变
    • 默认检测不规范的赋值与类型错误,让你的数据更加健壮

    安装

    npm install --save rc-redux-model 

    相关说明

    如何定义一个 model 并自动注册 action 及 reducers ?

    每一个 model 必须带有 namespace 、state,action 与 reducers 可不写,如需开启 immutable,需配置 openSeamlessImmutable = true,一个完整的 model 结构如下

    export default { namespace: '[your model.namespace]', state: { testA: '', testB: false, testC: [], testD: {}, }, } 

    rc-redux-model 会根据你的 state,每一个 state 的字段都会自动注册一个修改此 state 的 action,从而释放你键盘上的 CV 键, 例如 :

    state: { userName: 'oldValue' } 

    那么会自动为你注册一个 action,action 名以 set${stateName} 格式,如你的 stateName 为 : userName,那么会自动注册的 action 为 : setuserName

    action: { setuserName: ({ dispatch, getState, commit, call, currentAction }) => {} } 

    你只要在组件中调用此 action 即可修改 state 值 ( 不推荐使用这种 action 进行修改 state 值,推荐使用 setStore

    this.props.dispatch({ type: 'userModel/setuserName', payload: { userName: 'newValue', }, }) 

    问题来了,当 state 中的值很多(比如有几十个),那么为用户自动注册几十个 action,用户在使用上是否需要记住每一个 state 对应的 action 呢?这肯定是极其不合理的,所以一开始是提供一个默认的 action,用于修改所有的 state 值 ...

    随之而来的问题是,如果只提供一个 action,那么所有修改 State 的值都走的这个 action.type,在 redux-devtools-extension 中,会看不到具体的相对信息记录(因为都是同一个 action),最终,还是提供一个默认的 action,此 action 会根据用户提供的 payload.key,从而转发至对应的 action 中。

    对外提供统一默认 action,方面用户使用;对内根据 key,进行真实 action 的转发

    this.props.dispatch({ type: '[model.namespace]/setStore', payload: { key: [model.state.key] // 你要修改的 state key values: [your values] // 你要修改的值 } }) 

    所有修改 state 的 action,都通过 setStore 来发,不必担心在 redux devtools 中找不到,此 action 只是会根据你的 key,转发对应的 action 而已

    如何发送一个 action ?

    一个 action 由 type 、payload 组成,type 的命名规则为 : [model.namespace / actionName]

    // 下边是 namespace = appModel,actiOnName= fetchUserList 的例子 const action = { type: 'appModel/fetchUserList', } // 发起这个 action this.props.dispatch(action) 

    请注意,这里的每一个 action 都是 function, 也就是说,处理 同步 action 的思路跟处理 异步 action是一样的,如果你不明白, 请移步这里

    异步请求由谁处理 ?

    model.action 中,每一个 action 都是 function,它的回调参数为 :

    • dispatch : store 提供的 API,你可以调用 dispatch 继续分发 action
    • getState : store 提供的 API,通过该 API 你可以得到最新的 state
    • currentAction : 当前你 this.props.dispatch 的 action,你可以从这里拿到 typepayload
    • call : 替你转发请求,同时会使用 Promise 包裹,当然你可以自己写异步逻辑
    • commit : 接收一个 action,该方法用于 dispatch action 到 reducers,从而修改 state 值

    可以自己处理异步,再通过调用默认提供的 [model.namespace/setStore] 这个 action 进行修改 state 值

    如何在组件中获取 state 值?

    请注意,rc-redux-model 是一个中间件,并且大部分情况下,能够在你现有的项目中兼容,所以获取 state 的方式,还是跟你原来在组件中如何获取 state 一样

    一般来讲,我们的项目都会安装 react-redux 库,然后通过 connect 获取 state 上的值(没什么变化,你之前怎么写,现在就怎么写)

    class appComponent extends React.Component { componentDidMount() { // 发起 action,将 loading 状态改为 true this.props.dispatch({ type: 'appModel/fetchLoadingStatus', payload: { loadingStatus: true, }, }) } render() { const { loadingStatus } = this.props.appModel console.log(loadingStatus) // true } } const mapStateToProps = (state) => { return { appModel: state.appModel, reportTaskInfo: state.reportModel.taskInfo, // 其他 model 的值 } } export default connect(mapStateToProps)(appComponent) 

    如果很不幸,你项目中没安装 react-redux,那么你只能在每一个组件中,引入这个 store,然后通过 store.getState() 拿到 state 值了

    但是这种方式的缺陷就是,你要确保你的 state 是最新的,也就是你改完 state 值之后,需要重新 store.getState() 拿一下最新的值,这是比较麻烦的

    import store from '@your_folder/store' // 这个 store 就是你使用 Redux.createStore API 生成的 store class appComponent extends React.Component { constructor() { this.appState = store.getState()['appModel'] } } 

    数据不可变的(Immutable) ?

    在函数式编程语言中,数据是不可变的,所有的数据一旦产生,就不能改变其中的值,如果要改变,那就只能生成一个新的数据。如果有看过 redux 源码的小伙伴一定会知道,为什么每次都要返回一个新的 state,如果没听过, 可以看下这篇文章

    目前 rc-redux-model 内部集成了 seamless-immutable,提供一个 model 配置参数 openSeamlessImmutable,默认为 false,请注意,如果你的 state 是 Immutable,而在 model 中不设置此配置,那么会报错 !!!

    // 使用 seamless-immutable import Immutable from 'seamless-immutable' export default { namespace: 'appModel', state: Immutable({ username: '', }), openSeamlessImmutable: true, // 必须开启此配置 } 

    类型正确性 ?

    不可避免,有时在 model.state 中定义好某个值的类型,但在改的时候却将其改为另一个类型,例如 :

    export default { namespace: 'userModel', state: { name: '', // 这里定义 name 为 string 类型 }, } 

    但在修改此 state value 时,传递的确是一个非 string 类型的值

    this.props.dispatch({ type: 'userModel/setStore', payload: { key: 'name', values: {}, // 这里 name 变成了 object }, }) 

    这其实是不合理的,在 rc-redux-model 中,会判断 state[key] 中的类型与 payload 传入的类型进行比较,如果类型不相等,报错提示

    所有修改 state 的值,前提是 : 该值已经在 state 中定义,以下情况也会报错提示

    export default { namespace: 'userModel', state: { name: '', // 这里只定义 state 中存在 name }, } 

    此时想修改 state 中的另一属性值

    this.props.dispatch({ type: 'userModel/setStore', payload: { key: 'age', values: 18, // 这里想修改 age 属性的值 }, }) 

    极度不合理,因为你在 state 中并没有声明此属性,rc-redux-model 会默认帮你做检测

    使用

    如有疑问,看下边的相关说明~ 同时对于如何在项目中使用, 可以点这里

    提供默认 action,无需额外多写 action/reducers

    原先,我们想要修改 state 值,需要在 reducers 中定义好 action,但现在, rc-redux-model 提供默认的 action 用于修改,所以在 model 中,只需要定义 state 值即可

    export default { namespace: 'appModel', state: { value1: '', }, } 

    在页面中,只需要调用默认的 [model.namespace/setStore] 就可以修改 state 里的值了,美滋滋,不需要你自己在 action 、reducers 去写很多重复的修改 state 代码

    this.props.dispatch({ type: 'appModel/setStore', payload: { key: 'value1', values: 'appModel_state_value1', }, }) 

    复杂且真实的例子

    1. 新建一个 model 文件夹,该文件夹下新增一个 userModel.js
    // model/userModel.js import adapter from '@common/adapter' const userModel = { namespace: 'userModel', openSeamlessImmutable: false, state: { classId: '', studentList: [], userInfo: { name: 'PDK', }, }, action: { // demo: 发起一个异步请求,修改 global.model 的 loading 状态,异步请求结束之后,修改 reducers // 此异步逻辑,可自行处理,如果采用 call,那么会通过 Promise 包裹一层帮你转发 fetchUserInfo: async ({ dispatch, call }) => { // 请求前,将 globalModel 中的 loading 置为 true dispatch({ type: 'globalModel/changeLoadingStatus', payload: true, }) let res = await call(adapter.callAPI, params) if (res.code === 0) { dispatch({ type: 'userModel/setStore', payload: { key: 'userInfo', values: res.data, }, }) // 请求结束,将 globalModel 中的 loading 置为 false dispatch({ type: 'globalModel/changeLoadingStatus', payload: false, }) } return res }, }, } export default userModel 
    1. 聚集所有的 models,请注意,这里导出的是一个数组
    // model/index.js import userModel from './userModel' export default [userModel] 
    1. 处理 models,注册中间件
    // createStore.js import { createStore, applyMiddleware, combineReducers } from 'redux' import models from './models' import RcReduxModel from 'rc-redux-model' const reduxModel = new RcReduxModel(models) const reducerList = combineReducers(reduxModel.reducers) return createStore(reducerList, applyMiddleware(reduxModel.thunk)) 
    1. 在组件中调用
    class MyComponents extends React.PureComponent { componentDidMount() { // demo: 发起一个异步请求,修改 global.model 的 loading 状态,异步请求结束之后,修改 reducers // 具体的请求,在 model.action 中自己写,支持 Promise,之前需要 callback 回调请求后的数据,现在直接 then 获取 this.props .dispatch({ type: 'userModel/fetchUserInfo', }) .then((res) => { console.log(res) }) .catch((err) => { console.log(err) }) // demo1: 调用自动生成的默认 action,直接修改 state.userInfo 的值 (推荐此方法) this.props.dispatch({ type: 'userModel/setStore', payload: { key: 'userInfo', values: { name: 'setStore_name', }, }, }) // demo2: 调用自动生成的默认 action,直接修改 state.classId 的值 (推荐此方法) this.props.dispatch({ type: 'userModel/setStore', payload: { key: 'classId', values: 'sugarTeam2020', }, }) } } 

    hooks ?

    hooks 的出现,让我们看到了处理复杂且重复逻辑的曙光,那么问题来了,在 hooks 中能不能用 rc-redux-model,我想说 : “想啥呢,一个是 react 的特性,一个是 redux 的中间件, 冲突吗?”

    建议在 hooks 中把所有的业务逻辑,包括异步请求等都做了,调用 dispatch 只是为了修改 state 的值,这样你的 model 文件就极其干净,只需要写 namespacestate,action 和 reducers 都不需要写了,使用默认提供的 [model.namespace/setStore] 即可

    源码仓库地址 : rc-redux-model ,如果你觉得不错,求个 感谢老哥们

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5481 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 08:44 PVG 16:44 LAX 01:44 JFK 04:44
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86