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

  1. useReaction - 初始化函数,请在你的App组件内最开始的地方调用。 (接受一个可选参数来启用devtool,推荐在development模式下启用)
  2. useProvider - 调用此函数来获取Provider,用来包裹你的根节点。(在你的任意子组件中,若调用useProvider,拿到的其实是同一个,所以虽然不报错,但也并无额外的用处)
  3. useModel - 获取给定model实例的 {store, doAction, resetModel} ,详解如下:

    1. store - 此model的store,跟model有着同样的数据结构,但不是model本身,类似于[store] = useState(model)这种关系。
    2. doAction - action的执行器,框架内部会限定它只能修改该model的数据,是模块化思想的主要体现。此执行器(函数)接受3个参数,详解如下:

      1. action - 动作函数,用来处理业务逻辑,可以是普通函数或者sync/Promise 函数,可以返回 对model数据的修改部分(kv结构,k必须是model里定义过的字段名); 或者无返回值(只用来处理逻辑,不修改model数据);或者 返回用 justBack(data)包裹的数据,此数据会原样返回给doAction的调用处,但不修改model,不触发渲染。
      2. payload - 业务里调用doAction时,payload会原样传给 action 函数,
      3. showLoading - 执行此action期间,是否显示loading, 参数为 model or global , model 即此loading标记于此model, global 即此loading标记于全局, 默认空,详情参考 useLoading
      4. 注意: doAction 是 async function, 会返回action 函数返回的数据, 所以调用处可以拿到 action 函数的返回值。
      5. 注意: doAction是队列模式, 每次 doAction 都会进入队列, 所以多个doAction 是一个接一个执行的。
    3. resetModel - model数据的重置器,调用它会将model对应的store置回最初model定义时的初始数据。
  4. useLoading - 获取loding状态(true/false,只能获取,设置loading是在doAction时候传参数给showLoading),可以接受一个参数model,即取此model的loading状态,不传则取的是全局的loading状态,参看 doAction(someAction, payload, 'model' | 'global' | true)
  5. 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:

  1. 在App组件内最开始位置,调用useReaction 来初始化框架。若要启用devtool,就useReaction(true)

    export const App: React.FC = () => {
        /**init use-reaction */
        useReaction()
        ...
  2. 紧接着,调用 useProvider() 拿到Provider,用来包裹你的根结点:

    export const App: React.FC = () => {
            /**init use-reaction */
            useReaction()
    
            /**obtain Provider */
            const Provider = useProvider()
    
            /**render */
            return <Provider>
                      <GlobalLoading>
                         ...ChildNodes
                      </GlobalLoading>
                    </Provider>
        }
  3. 在某处,定义一个常量来保存你的一个业务模块的初始数据:

       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里预定义过的字段!!
          }
      }
  4. 业务里调用 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.


swellee
4 声望1 粉丝

前端开发者,偶尔装装全栈^O^