将React的开发思维扶正一点——借用useReducer 开发一个简化版的VM模型!

一、引子

    阴差阳错,造化弄人,面试Vue,入职React,逼不得已也要看一下React文档了。其实React语法和JSX,事件,props等都算简单,重点还是看Hooks,理解到底怎么玩才是正道!语言就是一种宗教,我很认同这句话,框架又何尝不是一种宗教,它制造一些术语,营造一些光环,然后把人的思想都拉拢过来,圈养为自己的信徒!虽然Vue 和 React的开发风格大相径庭,它们思想不同,API迥异,但从应用开发的角度上,肯定还是有最好的标准和规范的,也可以用相同的模式进行项目架构!MVVM的架构风格就是最好的风格,最好的代码实践,抛却组件要细分、高阶组件、props传值,多用Memo,shouldComponentUpdate  .......这些扰人心绪的东西,来看看React下的简单的MV HOOK吧!

二、目标----创建清晰的react的VM模型

       现在吃React这碗饭了,本着干一行,爱一行的心态,我恶补了一些Hook的知识,香,真香,都来尝尝。(Hook文档这块,官方文档连完整的API都没有,全是靠各个博客文章去理解,😅)

       我认为React 作为一个数一数二的MVVM框架,一直强调它的组件能力,Hooks特性,但它好像缺少了 VM的概念了,越来越来偏离MVVM框架了。VM就是把state和所有的action包装到一起的一个对象,不仅代码组织简单,还能有效避免jQuery时期那种基于事件驱动的开发模式中,容易引发的“事件纠缠”现象!

      我在之前的Vue3的项目中,构造了一种开发模式,我将其叫为pageState 或者pageCore模式,它其实就是指导如何编写一个页面的VM模型, 参考下图:

这就是一个典型的VM模型,有state和action,为了简化,action写为fn,  上面还有computed这种计算属性,当然这里也可以写watch函数!然后把这个VM模型直接往模板中去绑定即可。

   于是我的目标有了--------React是没有这种VM模型的Hook,所以我必须把React Hook揉成我要的姿势才行

三、成果----编写 Todo页面仅需要2步

         经过几天的尝试,终于初见成果。我引入2个新函数: useVM 和 genReducer来解决这个问题。 
         useVM的底层仍然是useReducer来实现的,它用来把状态和函数封装后,返回一个统一的VM模型
         genReducer 只是一个工具函数,把一个fn对象包装成reducer函数。
       这2个函数结构如下:

// 将初始值,fn,reducer传入, 返回一个vm模型,即{ state,fn }!
function useVM(rawState, rawFn, reducer){
   // 省略.........
   return {state,fn}
}

// 辅助函数,用于生成一个reducer函数
//         (它其实也可以写到useVM里面, 由于useVM执行多次,抽出来提高性能)
function genReducer(rawFn) {
   return function reducer(){
           // 省略..........
    }
}

    有了这两个函数,那我们就利用它们,写一个Todo Demo的页面吧!

第一步,编写useTodo 的自定义Hook,用它来生成一个vm.

useTodo函数其实就是todo页面的VM模型, 先看它书写后的整体结构: (具体代码在下面找)

       整体结构一共6个步骤,脉络比较清晰,所有步骤都是为了useTodo这个Hook服务的。

首先编写 ⑴初始state ⑵所有的fn方法, 这个fn是一个普通对象,还要把它转为一个 ⑶reducer函数,才能被useReducer所用。

⑴  ⑵ ⑶准备好了,就可以写⑷页面自定义Hook了,  把123传入useVM方法,返回⑹ {todoState, todoFn} 就可以模板可引用的 ,此时一个vm就完成了。

     如果需要计算属性和副作用函数,直接写在⑸扩展功能的位置 即可,此处useMemo  useEffect  useRef等所有的标准Hook都可以使用,任意发挥!

第二步,编写todo组件

    todo组件的编写非常简单、直白,首先引入 useTodo(), 获取一个vm模型,此后直接写JSX即可!

能够如此书写JSX,这都是useVM的功劳,极大简化了useReducer的使用模式。看了上面的代码,不知道各位看官们,是否接受这种VM模型+组件JSX的开始方式呢?

四、原理、规范和要点

1、原理

      useVM底层是一个useReducer在运行,它巧妙的把dispatch+reducer的代码模式,转换为 一个Js对象+一组 fn 的代码模式,这个清晰的VM模型,就简化了我们的使用方式和心智负担!函数调用的内部流程为:

todoFn.XXX(参数) 的函数调用   
        dispatch("xxxx",  参数)  
                reducer执行
                          原始的fn.XXX(preState,参数)调用 
                          返回一个键值对象给reducer函数
                更新state 
                组件update

2、代码编写规范和要点

  1. 编写state:   必须是JS对象格式,且只包含页面上原始状态值即可, 计算属性等值写在后面第5步中。
  2. 编写fn:  必须是JS对象格式,对应页面上所有的事件的地方。   详见示例代码!
          fn中的每一个方法,第一个参数必须是preState, 后面可以有任意参数。
          fn中方法为同步方法时,必须返回值是state的一部分的对象
          fn中的方法中包含异步逻辑时,必须返回一个Promise对象, 这个对象的reslove值必须是state的一部分的对象。 
           异步方法的命名必须以 Async 或 $ 结尾, 以区别同步方法。(已经统一同步、异步方法,不需要函数名来区分了)
  3. 生成reducer:  简单用genReducer(fn)  包裹一下即可。
  4. 调用 useVM : 传入state, fn ,reducer, 最终返回一个vm对象。
  5. 扩展属性、其它生命周期钩子,第三方hook的编写:
              扩展属性:相当于计算属性, 即可用原生的js编写,挂载到vm对象 上即可, 也可以用useMemo包裹一下。
              生命周期钩子:用useEffect可以模拟出 mount, update, unmount三种生命周期的逻辑。 这部分知识请多学习React Hook的知识!
              其它React内置Hook,第三方Hook: 均可以按需使用,比如useState,useRef,useCallback等。 
           当然与vm逻辑无关的hook,还是建议写到组件的函数顶部中去,没必要强行写在useVM这里
  6. 页面的编写:直接调用 useTodo函数返回vm模型后, 直接编写相应的JSX渲染函数即可!
                       页面中调用fn方法时,第一个参数preState是不能传递的,这个参数是在内部的reducer中,自动注入为参数的!
                        见下图示例:

五、Context----与子组件共享vm模型

       我是极不鼓励把页面拆分成多个细小的组件的,但是很多人会以拆分组件为荣,不拆分组件就各种不爽的情况。幸好我们借助useVM编写的VM模型其实是很容易跟子组件,深层组件共享的,方法就是使用React内置的Context概念。由于Context这里,我并没有做任何的封装工作,就是最官方的写法,所以我简单贴一下使用方法,供大家参考就算了!

父组件:

深层子组件

六、附录,源码

       感谢这几天学习Hook中,读的一些文章的作者们,我就不一一赘述了,因为我只看文章,没看你们的名字。来阅读我文章的人,也没必要看我的名字,能够不同的时空集合中,我们有过短暂的交流与感应已属万幸。
这个Hook的难点其实是处理异步函数, 可恶useReducer只支持同步的dispatch,在这块绕了一些弯路的!
我认真接触React时间短,虽然在React上,我认为有许多槽点,可能是源于我认知误区,以后React就是我的衣食父母了,还是要尊重一下了。

项目的源码上传到gitee的我的仓库中,感兴趣可以看一下。

源码仓库:  noonoo/react-Start (gitee.com)  里面有2个分支
        master分支:create-react-app创建的模板项目
        proj    分支: 使用 vite 创建的项目,且增加辅助功能。

目前proj分支包含:  useVm:底层用 state+fn +useReducer + Context 的模式实现的一个结构

                                 useRefVm:  底层使用 Es6 Class + useRef +forceUpdate  实现的一个结构(  推荐)

                                 Todo2示例: 底层使用Mobx 实现state +fn 实现vm

                                  TodoCls示例: 底层使用Mobx + Es6 Class 实现vm(  推荐)

 

 

 

    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章