react?hooks?UI與業(yè)務(wù)邏輯分離必要性技術(shù)方案
引言
當(dāng)前端業(yè)務(wù)復(fù)雜度上升到一定程度的時(shí)候,如何提升前端代碼質(zhì)量便成了老生常談的話題。似乎前端總逃不開(kāi)改他人代碼,重構(gòu),修復(fù)bug的宿命。那么,我們要如何從項(xiàng)目代碼層面,改變這一局面呢?才能保證項(xiàng)目A之于開(kāi)發(fā)者B也是能有條不紊的介入開(kāi)發(fā),從而最大程度降低人員開(kāi)銷,實(shí)現(xiàn)真正降本提效呢?
從代碼層面的問(wèn)題上看,我列舉了下,大概有如下幾種:
- 工程化沒(méi)有做好各類lint檢查和約束
超長(zhǎng)的function
- 很難單從函數(shù)名看出這個(gè)函數(shù)是作什么的
- 一個(gè)函數(shù)做了十件事
代碼量超長(zhǎng)的模塊
- 內(nèi)部維護(hù)了非常多邏輯,很難一眼看清某個(gè)變量是在哪里被修改的
- 一個(gè)組件夾雜了這個(gè)組件所需的所有代碼,不懂職責(zé)劃分的重要性
缺乏清晰的職責(zé)劃分
- 哪個(gè)模塊做什么,對(duì)于數(shù)據(jù)應(yīng)該如何流向,如何改變沒(méi)有清晰的認(rèn)知
數(shù)據(jù)流紊亂
- 缺乏函數(shù)式寫法的意識(shí)
變量命名極不規(guī)范
- 變量命名很含糊,能通過(guò)命名講清楚這個(gè)函數(shù)是做什么的,卻很隨意對(duì)待
檢驗(yàn)好代碼的唯一標(biāo)準(zhǔn)應(yīng)該是:人們能否輕而易舉地修改它
業(yè)務(wù)的問(wèn)題
我們知道,業(yè)務(wù)迭代往往排山倒海壓來(lái),一開(kāi)始如果不做好全局規(guī)劃,或者理清各個(gè)模塊的關(guān)系,是很難把控好進(jìn)度,進(jìn)而出現(xiàn)趕工導(dǎo)致bug滋生。那么,目前的hooks 業(yè)務(wù)組件的寫法有何問(wèn)題呢?
基于hooks的純業(yè)務(wù)組件寫法沒(méi)有做約束,ui與業(yè)務(wù)邏輯在一個(gè)函數(shù)內(nèi)部維護(hù),面條式代碼滋生,容易使組件業(yè)務(wù)邏輯代碼越寫越長(zhǎng),久而久之難以維護(hù)。很容易出現(xiàn)一個(gè)函數(shù)內(nèi)部耦合了types,constants,各類hooks(useState,useReducer,useCallback等),以及各種function,甚至是在dom層夾雜著非常多的邏輯處理。
慢慢地,復(fù)用性也會(huì)越來(lái)越差,可能需要經(jīng)常重構(gòu),抽離代碼 以達(dá)到復(fù)用的程度。但往往業(yè)務(wù)的排期已經(jīng)沒(méi)法抽開(kāi)身去維護(hù)老代碼,那怎么辦呢?
hooks組件的分離
《重構(gòu)2:改善既有代碼的設(shè)計(jì)》一文提到:把復(fù)雜的代碼塊分解為更小的單元,與好的命名一樣都很重要。
因此,我們需要在團(tuán)隊(duì)內(nèi)部達(dá)成共識(shí),能夠產(chǎn)出一種固定的開(kāi)發(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)]什么太大問(wèn)題,但這種代碼不內(nèi)聚,沒(méi)法提供通用的邏輯處理,一旦業(yè)務(wù)發(fā)生變化,就會(huì)引發(fā)多處代碼的維護(hù)危機(jī)。
其次如果有很多業(yè)務(wù)團(tuán)隊(duì),那么就需要考慮如何規(guī)范化統(tǒng)一團(tuán)隊(duì)內(nèi)部寫法,如何支持更健壯的業(yè)務(wù)代碼。
UI與邏輯分離并不是最終的目的,最終的目的應(yīng)該是形成一套易于維護(hù),模塊職責(zé)劃分清晰,能夠形成固定開(kāi)發(fā)模式,易于擴(kuò)展,能夠規(guī)范化業(yè)務(wù)使用場(chǎng)景,且具備強(qiáng)壯生命力的方案。
如果這種方式可以實(shí)現(xiàn)的話,那么為何很少有人會(huì)這么干呢?原因可能在于大家的函數(shù)式組件的思維。
在hooks還沒(méi)誕生之前,大家普遍對(duì)于函數(shù)式組件的認(rèn)知就是沒(méi)有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)類似子組件,父子組件通信的既視感隨之襲來(lái)。
接下來(lái),我們?cè)賮?lái)看下純hooks組件飽受大家詬病的一些問(wèn)題:
純hooks組件的問(wèn)題
1、useState 寫法難用,如果有很多state,需要一個(gè)個(gè)去維護(hù),寫法不夠簡(jiǎn)潔;當(dāng)業(yè)務(wù)邏輯越來(lái)越復(fù)雜,往往會(huì)出現(xiàn)一個(gè)模塊幾十個(gè)useState需要維護(hù)的尷尬局面。
2、useReducer + context的全局狀態(tài)難用,仍然需要定義很多action type,還需要提供provider,使用useReducer跨組件共享狀態(tài)很麻煩
3、useCallback 用法不夠清晰,不知何時(shí)用何時(shí)不用,用法造成困惑
4、 生命周期需要引入useEffect,需要手動(dòng)管理,且不夠語(yǔ)義化
5、基于hooks的業(yè)務(wù)組件,內(nèi)部方法依然難以做到復(fù)用,應(yīng)抽離出去單獨(dú)維護(hù)。
6、當(dāng)使用useEffect模擬mounted事件時(shí),處理異步請(qǐng)求函數(shù)時(shí)很麻煩。
7、當(dāng)組件達(dá)到一定復(fù)雜度的時(shí)候,堆積到一起的代碼會(huì)變得越來(lái)越難以維護(hù)
8、React Hook的閉包陷阱問(wèn)題
9、useState 調(diào)用updater更新后,無(wú)法同步獲取最新state值
10、useState updater無(wú)法實(shí)現(xiàn)細(xì)粒度更新對(duì)象的屬性值,不得不淺拷貝一份數(shù)據(jù)再進(jìn)行覆蓋
hooks-view-model
想要寫出健壯的,長(zhǎng)期可持續(xù)維護(hù)的代碼,就必須去理解這些在其他編程領(lǐng)域通用的設(shè)計(jì)模式、原則、范式。提高代碼質(zhì)量,除了依賴開(kāi)發(fā)自測(cè)和相關(guān)流程規(guī)范化外,也應(yīng)有相關(guān)工具或統(tǒng)一的開(kāi)發(fā)范式做約束。
對(duì)于純寫業(yè)務(wù)的人來(lái)說(shuō),沒(méi)有規(guī)范去強(qiáng)制約定,那么幾乎沒(méi)有人會(huì)這么處理業(yè)務(wù)邏輯與UI的關(guān)系,最終還是會(huì)寫到一起。這是hooks這種弱約束的弊端。
基于上述問(wèn)題,我開(kāi)發(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的十個(gè)“老大難”問(wèn)題
hooks-view-model是一種通過(guò)拆分UI視圖與業(yè)務(wù)邏輯的解決方案,可做到無(wú)需useReducer,無(wú)需redux等技術(shù)方案實(shí)現(xiàn)全局狀態(tài)更新而不會(huì)渲染無(wú)關(guān)組件。hooks-view-model是集狀態(tài)管理,變量的存儲(chǔ)管理和數(shù)據(jù)的持久化管理于一體的解決方案。
詳情點(diǎn)擊??:https://github.com/hawx1993/h...
hooks-view-model 主要用于分離UI與業(yè)務(wù)邏輯,可以解決 純hooks組件的問(wèn)題,對(duì)比一下hooks-view-model的優(yōu)勢(shì):
| hooks組件問(wèn)題 | hooks-view-model |
|---|---|
| useState 寫法難用,如果有很多state,需要一個(gè)個(gè)去維護(hù),寫法不夠簡(jiǎn)潔 | 可通過(guò)對(duì)象形式更新與解構(gòu)數(shù)據(jù),寫法簡(jiǎn)潔 |
| useReducer + context的全局狀態(tài)難用,仍然需要定義很多action type,還需要提供provider,使用useReducer跨組件共享狀態(tài)很麻煩 | 全局狀態(tài)更新只需使用useGlobalStatehooks,用法簡(jiǎn)單 |
| 生命周期需要引入useEffect,需要手動(dòng)管理,且不夠語(yǔ)義化 | 提供mounted和unmounted 鉤子函數(shù),可自動(dòng)執(zhí)行,語(yǔ)義化友好 |
| 基于hooks的業(yè)務(wù)組件,內(nèi)部方法依然難以做到復(fù)用,應(yīng)抽離出去單獨(dú)維護(hù) | class 寫法可通過(guò)繼承 實(shí)現(xiàn)復(fù)用,還可以通過(guò)useVM引入其他viewModel進(jìn)行復(fù)用,復(fù)用性高 |
| 當(dāng)接收新的props,需要手動(dòng)使用useEffect觀察props變化,沒(méi)有直接的鉤子可以自動(dòng)觸發(fā) | class 提供onPropsChanged 鉤子函數(shù),可自動(dòng)觸發(fā)執(zhí)行 |
| 當(dāng)組件達(dá)到一定復(fù)雜度的時(shí)候,堆積到一起的代碼會(huì)變得越來(lái)越難以維護(hù) | UI與邏輯做到了很好的分離,代碼組織性強(qiáng) |
| React Hook的閉包陷阱問(wèn)題 | 由于方法都提到class中去維護(hù)了,所以不存在此問(wèn)題 |
| useState 調(diào)用updater更新后,無(wú)法同步獲取最新state值 | 可通過(guò)調(diào)用getCurrentState 同步獲取最新值 |
| 調(diào)用updater無(wú)法實(shí)現(xiàn)細(xì)粒度更新對(duì)象屬性值,需淺拷貝對(duì)象后覆蓋 | 可通過(guò)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方法,只不過(guò)需要保證class中的所有方法都是箭頭函數(shù),否則會(huì)報(bào)錯(cuò)
// 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ū)別
誠(chéng)然,hooks 可滿足UI與邏輯分離的需求,但抽離無(wú)法被公用的業(yè)務(wù)邏輯到hooks中是否有必要?與可復(fù)用的hooks 是否容易造成混淆?hooks存在的useCallback,useReducer,以及對(duì)副作用的使用等容易造成使用困惑的,以及對(duì)useState 使用上的麻煩是否可以有其他方法簡(jiǎn)化?
其次,函數(shù)式組件的寫法也并非函數(shù)式編程,相同的輸入(props)并不會(huì)得到相同的輸出(內(nèi)部的state或全局的state都可能對(duì)結(jié)果產(chǎn)生影響)。
而業(yè)務(wù)邏輯抽離到class中,依然是函數(shù)式組件。class相比于function 天然的具有可組織性,可擴(kuò)展性(extends),和可維護(hù)性。
首先,業(yè)務(wù)邏輯是比較復(fù)雜的,Class 具備繼承能力,可實(shí)現(xiàn)viewModel與view都獲得來(lái)自父類的能力;
其次,class 能夠更好維護(hù)業(yè)務(wù)邏輯代碼,在class中寫業(yè)務(wù)邏輯,完全可以忽視r(shí)eact hooks自帶的各種hooks,諸如useRef,useCallback,useReducer,useState等,寫起業(yè)務(wù)邏輯來(lái)更加純粹;
再者,hooks 也可以與viewModel共存,只需要在view中引入hooks,然后將返回值作為props,通過(guò)useVM傳給viewModel即可,兩者是共存的,并不是互斥的。
基于class的viewModel可以更好的維護(hù)業(yè)務(wù)邏輯代碼,可以使用裝飾器,public,private等關(guān)鍵字,顯示提高代碼可維護(hù)性和擴(kuò)展能力。而可復(fù)用的hooks可以用來(lái)抽象業(yè)務(wù)邏輯實(shí)現(xiàn)副作用觀察和邏輯復(fù)用,兩者具有不同的心智模型。
配置生成項(xiàng)目模板文件
此外,我還在hooks-view-model內(nèi)置了項(xiàng)目的模板文件,可一鍵生成所需模板文件和代碼,這樣便可以讓各個(gè)業(yè)務(wù)線的前端團(tuán)隊(duì)始終保持一致的開(kāi)發(fā)規(guī)范和風(fēng)格。
可以真正做到成員B可以低成本介入項(xiàng)目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
指明模板需要生成的相對(duì)路徑地址:
const dir_to_generate = './src/pages/'; module.exports = dir_to_generate;
執(zhí)行完后,便會(huì)在指定的目錄下生成如下模板文件:

更好的debug能力
使用hooks,我們?nèi)绻胫喇?dāng)前的state值,我們需要一個(gè)個(gè)console出來(lái),而基于hooks-view-model,我們只需要在控制臺(tái)輸入:globalStore,即可查看所有view對(duì)應(yīng)的state,通過(guò)key區(qū)分??纱蟠筇嵘齞ebug能力。

以上就是react hooks UI與業(yè)務(wù)邏輯分離必要性技術(shù)方案的詳細(xì)內(nèi)容,更多關(guān)于react hooks UI業(yè)務(wù)邏輯分離的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React實(shí)時(shí)預(yù)覽react-live源碼解析
這篇文章主要為大家介紹了React實(shí)時(shí)預(yù)覽react-live源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
關(guān)于React Native 無(wú)法鏈接模擬器的問(wèn)題
許多朋友遇到React Native 無(wú)法鏈接模擬器的問(wèn)題,怎么解決呢,本文給大家分享完整簡(jiǎn)便解決方法及配置例題,對(duì)React Native 鏈接模擬器相關(guān)知識(shí)感興趣的朋友一起看看吧2021-06-06
react中useState使用:如何實(shí)現(xiàn)在當(dāng)前表格直接更改數(shù)據(jù)
這篇文章主要介紹了react中useState的使用:如何實(shí)現(xiàn)在當(dāng)前表格直接更改數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
詳解React-Native全球化多語(yǔ)言切換工具庫(kù)react-native-i18n
這篇文章主要介紹了詳解React-Native全球化語(yǔ)言切換工具庫(kù)react-native-i18n,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
React jsx轉(zhuǎn)換與createElement使用超詳細(xì)講解
這篇文章主要介紹了React jsx轉(zhuǎn)換與createElement使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-11-11
react-intl實(shí)現(xiàn)React國(guó)際化多語(yǔ)言的方法
這篇文章主要介紹了react-intl實(shí)現(xiàn)React國(guó)際化多語(yǔ)言的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

