npm 链接:use-reaction
简介
react app 状态管理(数据管理)一直是项目开发中必不可少的环节。小型项目在各自组件中使用useState / setSate 即可。但稍微复杂一点的项目,一遍都会考虑引入状态管理框架,如redux, mobx, dva等等。
对于复杂的项目,我一向推崇使用模块化的思想来管理数据。去年我曾发布过一个基于redux的二次封装package,用于模块化的管理业务数据,有兴趣的可以考古参考module-reaction.
但现在React Hook模式的愈发成熟和流行,促使社区形成一股去redux风潮。我们项目组的同学也抱怨说redux太重了,于是,我用闲暇时间,基于hook发布了这个新包:use-reaction , 并配备了简易了的chrome devtool,新鲜出炉,欢迎品鉴!
use-reaction
前面说了,这是一个基于hook的模块化数据管理框架,体积极小,只有约100行代码!
api和使用方式也尽量简化。
repo
github仓库: Repo, 记得来给加个star哦!!!
chrome-dev-tools
chrome 插件,用户查看模块数据,查看action历史,查看action改变的数据 等等,
- 安装:用法是下载之后,打开你的chrome,打开【设置】->【管理扩展程序】,然后把下载好的crx文件拖到chrome窗口内,完成安装。download!
- 使用:首先你的React项目中,调用useReaction方法时,传入参数true以启用devtool
- 然后,chrome打开调试面板(按f12),你会看到‘Elements,Console,Sources,Network...'等一排工具,继续往后面找,找到UseReaction,点击打开。
(保持chrome调试面板开着)刷新你的app页面,或者在你的app里触发一个action,那么devtool就可以track到你应用中的数据了。
基于安全考虑,UseReaction devtool只能track你的应用数据,并不能反过来修改你的app内的数据。
ps: devtool的源码也在 github Repo当中,有兴趣可以自行查看。再次求star.
install
npm i use-reaction
apis
useReaction
- 初始化函数,请在你的App组件内最开始的地方调用。 (接受一个可选参数来启用devtool,推荐在development模式下启用)useProvider
- 调用此函数来获取Provider,用来包裹你的根节点。(在你的任意子组件中,若调用useProvider,拿到的其实是同一个,所以虽然不报错,但也并无额外的用处)useModel
- 获取给定model实例的 {store, doAction, resetModel} ,详解如下:store
- 此model的store,跟model有着同样的数据结构,但不是model本身,类似于[store] = useState(model)这种关系。doAction
- action的执行器,框架内部会限定它只能修改该model的数据,是模块化思想的主要体现。此执行器(函数)接受3个参数,详解如下:- action - 动作函数,用来处理业务逻辑,可以是普通函数或者sync/Promise 函数,可以返回 对model数据的修改部分(kv结构,k必须是model里定义过的字段名); 或者无返回值(只用来处理逻辑,不修改model数据);或者 返回用
justBack(data)
包裹的数据,此数据会原样返回给doAction的调用处,但不修改model,不触发渲染。 - payload - 业务里调用doAction时,payload会原样传给 action 函数,
- showLoading - 执行此action期间,是否显示loading, 参数为 model or global ,
model
即此loading标记于此model,global
即此loading标记于全局, 默认空,详情参考useLoading
- 注意: doAction 是 async function, 会返回action 函数返回的数据, 所以调用处可以拿到 action 函数的返回值。
- 注意: doAction是队列模式, 每次 doAction 都会进入队列, 所以多个doAction 是一个接一个执行的。
- action - 动作函数,用来处理业务逻辑,可以是普通函数或者sync/Promise 函数,可以返回 对model数据的修改部分(kv结构,k必须是model里定义过的字段名); 或者无返回值(只用来处理逻辑,不修改model数据);或者 返回用
resetModel
- model数据的重置器,调用它会将model对应的store置回最初model定义时的初始数据。
useLoading
- 获取loding状态(true/false,只能获取,设置loading是在doAction时候传参数给showLoading),可以接受一个参数model,即取此model的loading状态,不传则取的是全局的loading状态,参看doAction(someAction, payload, 'model' | 'global' | true)
justBack
- 有时候,你的action函数可能只想偷偷摸摸干点啥事,并把结果告诉调用方,同时又不想惊动管理者,那就用这个吧!在你action里返回数据的时候,用justBack包裹你的数据,就能只是把数据返回给调用处,但不修改model store也不触发渲染:export const actionJustBackData: Action<typeof model_b> = async function({ payload }) { ... do process task ... return justBack('hello' + payload) }
How to use
实例讲解4个简单步骤来使用use-reaction:
在App组件内最开始位置,调用useReaction 来初始化框架。若要启用devtool,就useReaction(true)
export const App: React.FC = () => { /**init use-reaction */ useReaction() ...
紧接着,调用 useProvider() 拿到Provider,用来包裹你的根结点:
export const App: React.FC = () => { /**init use-reaction */ useReaction() /**obtain Provider */ const Provider = useProvider() /**render */ return <Provider> <GlobalLoading> ...ChildNodes </GlobalLoading> </Provider> }
在某处,定义一个常量来保存你的一个业务模块的初始数据:
export const model_a: ModelA = { a: 1, aa: { aa: 1 }, }
同时,要想修改模块数据,需要通过action,所以,再定义若干action吧,
列如:export const actionTestA: Action<ModelA> = ({ payload, store }) => { // 下一行会报错, 因为action中不能直接修改store的数据,而是应该以返回值的形式修改模块数据 // store.a = 6 // 只返回修改的部分,无需{...store,a: xxx}这样的全量形式! return { a: store.a + payload, sth: 'hello world' // 这一行’hello world'虽然返回出去了,但会被框架忽略,因为只允许修改model里预定义过的字段!! } }
业务里调用 useModel(model_a) 来取model store,以及执行action 以下是一个完整的使用实例代码:
export const App: React.FC = () => { /**init use-reaction */ useReaction(true) /**obtain Provider */ const Provider = useProvider() /**render */ return <Provider> <GlobalLoading> <SubPageA /> <SubPageB> <CompC /> </SubPageB> </GlobalLoading> <CompC /> </Provider> } function GlobalLoading(props: KV) { const loading = useLoading() console.log('loading:', loading) return < Spin spinning={loading} > {props.children} </Spin > } //--------examples of how to use--------- function SubPageA(props?: KV) { const { store, doAction } = useModel(model_a) const { store: storeB, doAction: doActionB } = useModel(model_b) const onfinish = async (values: any) => { console.log('values', values) await doAction(actionTestA, 2, 'global') console.log('hello hello') } return ( <div className="page page-a"> <h3>page A</h3> <div> value 'A' is {store.a} </div> <div> value 'B' is {storeB.b} </div> <Form onFinish={onfinish}> <Form.Item label="email" name="email"><Input /></Form.Item> <Form.Item label="password" name="password"><Password /></Form.Item> <Button htmlType="submit">increase A with global loading</Button> </Form> <button onClick={async e => { const backed = await doActionB(actionJustBackData, ',world:' + Date.now()) alert(backed) }}>just back data</button> </div> ) } function SubPageB(props: KV) { const { store, doAction } = useModel(model_b) const loading = useLoading(model_b) console.log('model render loading', loading) return ( <Spin spinning={loading}> <div className="page page-b"> <h3>page B</h3> <div> value B is {store.b} </div> <button onClick={e => { doAction(actionTestB, 'do action with loading', 'model') }}>Increse B with model loading</button> <h6>see my child compenent below:</h6> {props.children} </div> </Spin> ) } function CompC() { const { store, resetModel, doAction } = useModel(model_a) const { store: storeB, resetModel: resetModelB } = useModel(model_b) return <div className="comp"> <p>the values in model_a:</p> <ul> <li><span>Value A:</span><span>{store.a}</span></li> <li><span>Value AA:</span><span>{store.aa.aa}</span></li> </ul> <button onClick={resetModel}>reset model_a</button> <hr /> <p>the values in model_b:</p> <ul> <li><span>Value B:</span><span>{storeB.b}</span></li> </ul> <hr /> <button onClick={resetModelB}>reset model_b</button> <button onClick={e => doAction(actionTestA, null, 'global')}> do loading</button> </div> }
很多细节,参考 example
欢迎star, issue.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。