乐趣区

关于前端:usereaction-精致的基于hook的模块化数据管理方案

简介

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.

退出移动版