將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(  推薦)

 

 

 

    

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