react?hooks?UI與業(yè)務(wù)邏輯分離必要性技術(shù)方案
引言
當(dāng)前端業(yè)務(wù)復(fù)雜度上升到一定程度的時候,如何提升前端代碼質(zhì)量便成了老生常談的話題。似乎前端總逃不開改他人代碼,重構(gòu),修復(fù)bug的宿命。那么,我們要如何從項目代碼層面,改變這一局面呢?才能保證項目A之于開發(fā)者B也是能有條不紊的介入開發(fā),從而最大程度降低人員開銷,實(shí)現(xiàn)真正降本提效呢?
從代碼層面的問題上看,我列舉了下,大概有如下幾種:
- 工程化沒有做好各類lint檢查和約束
超長的function
- 很難單從函數(shù)名看出這個函數(shù)是作什么的
- 一個函數(shù)做了十件事
代碼量超長的模塊
- 內(nèi)部維護(hù)了非常多邏輯,很難一眼看清某個變量是在哪里被修改的
- 一個組件夾雜了這個組件所需的所有代碼,不懂職責(zé)劃分的重要性
缺乏清晰的職責(zé)劃分
- 哪個模塊做什么,對于數(shù)據(jù)應(yīng)該如何流向,如何改變沒有清晰的認(rèn)知
數(shù)據(jù)流紊亂
- 缺乏函數(shù)式寫法的意識
變量命名極不規(guī)范
- 變量命名很含糊,能通過命名講清楚這個函數(shù)是做什么的,卻很隨意對待
檢驗(yàn)好代碼的唯一標(biāo)準(zhǔn)應(yīng)該是:人們能否輕而易舉地修改它
業(yè)務(wù)的問題
我們知道,業(yè)務(wù)迭代往往排山倒海壓來,一開始如果不做好全局規(guī)劃,或者理清各個模塊的關(guān)系,是很難把控好進(jìn)度,進(jìn)而出現(xiàn)趕工導(dǎo)致bug滋生。那么,目前的hooks 業(yè)務(wù)組件的寫法有何問題呢?
基于hooks的純業(yè)務(wù)組件寫法沒有做約束,ui與業(yè)務(wù)邏輯在一個函數(shù)內(nèi)部維護(hù),面條式代碼滋生,容易使組件業(yè)務(wù)邏輯代碼越寫越長,久而久之難以維護(hù)。很容易出現(xiàn)一個函數(shù)內(nèi)部耦合了types,constants,各類hooks(useState,useReducer,useCallback等),以及各種function,甚至是在dom層夾雜著非常多的邏輯處理。
慢慢地,復(fù)用性也會越來越差,可能需要經(jīng)常重構(gòu),抽離代碼 以達(dá)到復(fù)用的程度。但往往業(yè)務(wù)的排期已經(jīng)沒法抽開身去維護(hù)老代碼,那怎么辦呢?
hooks組件的分離
《重構(gòu)2:改善既有代碼的設(shè)計》一文提到:把復(fù)雜的代碼塊分解為更小的單元,與好的命名一樣都很重要。
因此,我們需要在團(tuán)隊內(nèi)部達(dá)成共識,能夠產(chǎn)出一種固定的開發(fā)范式,能夠分離代碼,做到職責(zé)清晰,例如:A模塊專門處理View視圖組件,B模塊專門處理業(yè)務(wù)邏輯,C模塊專門維護(hù)ts類型types,D模塊專門維護(hù)各類常量constants,E模塊專門維護(hù)公用hooks邏輯,F(xiàn)模塊專門維護(hù)css modules等。
那么,在這前提之下,我們需要實(shí)現(xiàn)前端UI與業(yè)務(wù)邏輯分離,目前主流的有兩種方式,一種是純邏輯抽離出去,返回函數(shù)內(nèi)部方法和state;形如:
const useApp = () => { const [name, setName] = useState('mike'); const getName = () => {}; const updateName = () => {}; return { name, getName, updateName } } const AppView =() => { const { name } = useApp(); return <div>{name}</div> }
這種方式?jīng)]什么太大問題,但這種代碼不內(nèi)聚,沒法提供通用的邏輯處理,一旦業(yè)務(wù)發(fā)生變化,就會引發(fā)多處代碼的維護(hù)危機(jī)。
其次如果有很多業(yè)務(wù)團(tuán)隊,那么就需要考慮如何規(guī)范化統(tǒng)一團(tuán)隊內(nèi)部寫法,如何支持更健壯的業(yè)務(wù)代碼。
UI與邏輯分離并不是最終的目的,最終的目的應(yīng)該是形成一套易于維護(hù),模塊職責(zé)劃分清晰,能夠形成固定開發(fā)模式,易于擴(kuò)展,能夠規(guī)范化業(yè)務(wù)使用場景,且具備強(qiáng)壯生命力的方案。
如果這種方式可以實(shí)現(xiàn)的話,那么為何很少有人會這么干呢?原因可能在于大家的函數(shù)式組件的思維。
在hooks還沒誕生之前,大家普遍對于函數(shù)式組件的認(rèn)知就是沒有state,所以當(dāng)props是固定的,那么函數(shù)式組件每次渲染結(jié)果也都是一樣的,也就是相同的輸入總能得到相同的輸出。但現(xiàn)在hooks出現(xiàn)了,函數(shù)組件內(nèi)部可以維護(hù)state了,相同的輸入并不一定能得到相同的輸出了。
此外,這種方式與可復(fù)用的hooks的區(qū)別又在哪里,如果兩種都使用hooks維護(hù),又如何區(qū)分呢?
另外一種方式就是保留業(yè)務(wù)邏輯,但把UI組件抽離出去,這種方式更不推薦了。有點(diǎn)類似子組件,父子組件通信的既視感隨之襲來。
接下來,我們再來看下純hooks組件飽受大家詬病的一些問題:
純hooks組件的問題
1、useState 寫法難用,如果有很多state,需要一個個去維護(hù),寫法不夠簡潔;當(dāng)業(yè)務(wù)邏輯越來越復(fù)雜,往往會出現(xiàn)一個模塊幾十個useState需要維護(hù)的尷尬局面。
2、useReducer + context
的全局狀態(tài)難用,仍然需要定義很多action type
,還需要提供provider,使用useReducer跨組件共享狀態(tài)很麻煩
3、useCallback 用法不夠清晰,不知何時用何時不用,用法造成困惑
4、 生命周期需要引入useEffect,需要手動管理,且不夠語義化
5、基于hooks的業(yè)務(wù)組件,內(nèi)部方法依然難以做到復(fù)用,應(yīng)抽離出去單獨(dú)維護(hù)。
6、當(dāng)使用useEffect模擬mounted事件時,處理異步請求函數(shù)時很麻煩。
7、當(dāng)組件達(dá)到一定復(fù)雜度的時候,堆積到一起的代碼會變得越來越難以維護(hù)
8、React Hook的閉包陷阱問題
9、useState 調(diào)用updater更新后,無法同步獲取最新state值
10、useState updater無法實(shí)現(xiàn)細(xì)粒度更新對象的屬性值,不得不淺拷貝一份數(shù)據(jù)再進(jìn)行覆蓋
hooks-view-model
想要寫出健壯的,長期可持續(xù)維護(hù)的代碼,就必須去理解這些在其他編程領(lǐng)域通用的設(shè)計模式、原則、范式。提高代碼質(zhì)量,除了依賴開發(fā)自測和相關(guān)流程規(guī)范化外,也應(yīng)有相關(guān)工具或統(tǒng)一的開發(fā)范式做約束。
對于純寫業(yè)務(wù)的人來說,沒有規(guī)范去強(qiáng)制約定,那么幾乎沒有人會這么處理業(yè)務(wù)邏輯與UI的關(guān)系,最終還是會寫到一起。這是hooks這種弱約束的弊端。
基于上述問題,我開發(fā)了基于react hooks的UI與業(yè)務(wù)邏輯分離的方案,內(nèi)部基于useState hooks的updater 實(shí)現(xiàn)??蓪?shí)現(xiàn)在class內(nèi)部setState,然后在View組件中響應(yīng)更新?;窘鉀Q了上述react hooks的十個“老大難”問題
hooks-view-model
是一種通過拆分UI視圖與業(yè)務(wù)邏輯的解決方案,可做到無需useReducer,無需redux等技術(shù)方案實(shí)現(xiàn)全局狀態(tài)更新而不會渲染無關(guān)組件。hooks-view-model
是集狀態(tài)管理,變量的存儲管理和數(shù)據(jù)的持久化管理于一體的解決方案。
詳情點(diǎn)擊??:https://github.com/hawx1993/h...
hooks-view-model
主要用于分離UI與業(yè)務(wù)邏輯,可以解決 純hooks組件的問題,對比一下hooks-view-model的優(yōu)勢:
hooks組件問題 | hooks-view-model |
---|---|
useState 寫法難用,如果有很多state,需要一個個去維護(hù),寫法不夠簡潔 | 可通過對象形式更新與解構(gòu)數(shù)據(jù),寫法簡潔 |
useReducer + context的全局狀態(tài)難用,仍然需要定義很多action type,還需要提供provider,使用useReducer跨組件共享狀態(tài)很麻煩 | 全局狀態(tài)更新只需使用useGlobalStatehooks,用法簡單 |
生命周期需要引入useEffect,需要手動管理,且不夠語義化 | 提供mounted和unmounted 鉤子函數(shù),可自動執(zhí)行,語義化友好 |
基于hooks的業(yè)務(wù)組件,內(nèi)部方法依然難以做到復(fù)用,應(yīng)抽離出去單獨(dú)維護(hù) | class 寫法可通過繼承 實(shí)現(xiàn)復(fù)用,還可以通過useVM引入其他viewModel進(jìn)行復(fù)用,復(fù)用性高 |
當(dāng)接收新的props,需要手動使用useEffect觀察props變化,沒有直接的鉤子可以自動觸發(fā) | class 提供onPropsChanged 鉤子函數(shù),可自動觸發(fā)執(zhí)行 |
當(dāng)組件達(dá)到一定復(fù)雜度的時候,堆積到一起的代碼會變得越來越難以維護(hù) | UI與邏輯做到了很好的分離,代碼組織性強(qiáng) |
React Hook的閉包陷阱問題 | 由于方法都提到class中去維護(hù)了,所以不存在此問題 |
useState 調(diào)用updater更新后,無法同步獲取最新state值 | 可通過調(diào)用getCurrentState 同步獲取最新值 |
調(diào)用updater無法實(shí)現(xiàn)細(xì)粒度更新對象屬性值,需淺拷貝對象后覆蓋 | 可通過updateImmerState實(shí)現(xiàn)細(xì)粒度更新 |
1、View:獲取數(shù)據(jù)并展示數(shù)據(jù)
// AppView.tsx import { AppViewModel } from './AppViewModel' import { useVM } from 'hooks-view-model' import { usePrevious } from '@/hooks'; const AppView = () => { const { perviousAddress } = usePrevious(); const { changeAddress, useCurrentState } = useVM(AppViewModel, { address: perviousAddress, }) const { address = 'ZheJiang Province' } = useCurrentState() return ( <div> <button onClick={changeAddress}>click to change address</button> <span>{address}</span> </div> ) }
2、ViewModel:管理狀態(tài)和處理數(shù)據(jù)
updateGlobalStateByKey
和 updateCurrentState
相當(dāng)于在class中可以使用的setState方法,只不過需要保證class中的所有方法都是箭頭函數(shù),否則會報錯
// AppViewModel.ts import StoreViewModel from 'hooks-view-model' class AppViewModel extends StoreViewModel { changeAddress = () => { this.updateCurrentState(this.props.address);// 相當(dāng)于setState } } export { AppViewModel }
那么可能有很多人就疑惑了,明明react官方已經(jīng)推崇函數(shù)式寫法了,為什么還要用class?
基于class的viewModel寫法與hooks有什么區(qū)別
誠然,hooks 可滿足UI與邏輯分離的需求,但抽離無法被公用的業(yè)務(wù)邏輯到hooks中是否有必要?與可復(fù)用的hooks 是否容易造成混淆?hooks存在的useCallback,useReducer,以及對副作用的使用等容易造成使用困惑的,以及對useState 使用上的麻煩是否可以有其他方法簡化?
其次,函數(shù)式組件的寫法也并非函數(shù)式編程,相同的輸入(props)并不會得到相同的輸出(內(nèi)部的state或全局的state都可能對結(jié)果產(chǎn)生影響)。
而業(yè)務(wù)邏輯抽離到class中,依然是函數(shù)式組件。class相比于function 天然的具有可組織性,可擴(kuò)展性(extends),和可維護(hù)性。
首先,業(yè)務(wù)邏輯是比較復(fù)雜的,Class 具備繼承能力,可實(shí)現(xiàn)viewModel與view都獲得來自父類的能力;
其次,class 能夠更好維護(hù)業(yè)務(wù)邏輯代碼,在class中寫業(yè)務(wù)邏輯,完全可以忽視react hooks自帶的各種hooks,諸如useRef,useCallback,useReducer,useState
等,寫起業(yè)務(wù)邏輯來更加純粹;
再者,hooks 也可以與viewModel共存,只需要在view中引入hooks,然后將返回值作為props,通過useVM傳給viewModel即可,兩者是共存的,并不是互斥的。
基于class的viewModel可以更好的維護(hù)業(yè)務(wù)邏輯代碼,可以使用裝飾器,public,private等關(guān)鍵字,顯示提高代碼可維護(hù)性和擴(kuò)展能力。而可復(fù)用的hooks可以用來抽象業(yè)務(wù)邏輯實(shí)現(xiàn)副作用觀察和邏輯復(fù)用,兩者具有不同的心智模型。
配置生成項目模板文件
此外,我還在hooks-view-model內(nèi)置了項目的模板文件,可一鍵生成所需模板文件和代碼,這樣便可以讓各個業(yè)務(wù)線的前端團(tuán)隊始終保持一致的開發(fā)規(guī)范和風(fēng)格。
可以真正做到成員B可以低成本介入項目A中,提高代碼的可維護(hù)性,可閱讀性。用法如下:
執(zhí)行如下步驟,可一鍵生成模板文件
1、添加腳本命令
在package.json的scripts中添加如下腳本命令
scripts: { "generate": "plop --plopfile ./node_modules/hooks-view-model/generators/index.js" }
2、根目錄創(chuàng)建template.config.js
指明模板需要生成的相對路徑地址:
const dir_to_generate = './src/pages/'; module.exports = dir_to_generate;
執(zhí)行完后,便會在指定的目錄下生成如下模板文件:
更好的debug能力
使用hooks,我們?nèi)绻胫喇?dāng)前的state值,我們需要一個個console出來,而基于hooks-view-model,我們只需要在控制臺輸入:globalStore,即可查看所有view對應(yīng)的state,通過key區(qū)分??纱蟠筇嵘齞ebug能力。
以上就是react hooks UI與業(yè)務(wù)邏輯分離必要性技術(shù)方案的詳細(xì)內(nèi)容,更多關(guān)于react hooks UI業(yè)務(wù)邏輯分離的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react項目實(shí)踐之webpack-dev-serve
這篇文章主要介紹了react項目實(shí)踐之webpack-dev-serve,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09React之錯誤邊界 Error Boundaries示例詳解
這篇文章主要為大家介紹了React之錯誤邊界Error Boundaries示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10react在安卓中輸入框被手機(jī)鍵盤遮擋問題的解決方法
這篇文章主要給大家介紹了關(guān)于react在安卓中輸入框被手機(jī)鍵盤遮擋問題的解決方法,文中通過圖文以及示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧2018-09-09詳解React Native頂|底部導(dǎo)航使用小技巧
本篇文章主要介紹了詳解React Native頂|底部導(dǎo)航使用小技巧 ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09React 使用browserHistory項目訪問404問題解決
這篇文章主要介紹了React 使用browserHistory項目訪問404問題解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06關(guān)于react+antd樣式不生效問題的解決方式
最近本人在使用Antd開發(fā)時遇到些問題,所以下面這篇文章主要給大家介紹了關(guān)于react+antd樣式不生效問題的解決方式,文中通過圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07React中使用Axios發(fā)起POST請求提交文件方式
這篇文章主要介紹了React中使用Axios發(fā)起POST請求提交文件方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02