網站首頁 編程語言 正文
引言
當前端業務復雜度上升到一定程度的時候,如何提升前端代碼質量便成了老生常談的話題。似乎前端總逃不開改他人代碼,重構,修復bug的宿命。那么,我們要如何從項目代碼層面,改變這一局面呢?才能保證項目A之于開發者B也是能有條不紊的介入開發,從而最大程度降低人員開銷,實現真正降本提效呢?
從代碼層面的問題上看,我列舉了下,大概有如下幾種:
- 工程化沒有做好各類lint檢查和約束
超長的function
- 很難單從函數名看出這個函數是作什么的
- 一個函數做了十件事
代碼量超長的模塊
- 內部維護了非常多邏輯,很難一眼看清某個變量是在哪里被修改的
- 一個組件夾雜了這個組件所需的所有代碼,不懂職責劃分的重要性
缺乏清晰的職責劃分
- 哪個模塊做什么,對于數據應該如何流向,如何改變沒有清晰的認知
數據流紊亂
- 缺乏函數式寫法的意識
變量命名極不規范
- 變量命名很含糊,能通過命名講清楚這個函數是做什么的,卻很隨意對待
檢驗好代碼的唯一標準應該是:人們能否輕而易舉地修改它
業務的問題
我們知道,業務迭代往往排山倒海壓來,一開始如果不做好全局規劃,或者理清各個模塊的關系,是很難把控好進度,進而出現趕工導致bug滋生。那么,目前的hooks 業務組件的寫法有何問題呢?
基于hooks的純業務組件寫法沒有做約束,ui與業務邏輯在一個函數內部維護,面條式代碼滋生,容易使組件業務邏輯代碼越寫越長,久而久之難以維護。很容易出現一個函數內部耦合了types,constants,各類hooks(useState,useReducer,useCallback等),以及各種function,甚至是在dom層夾雜著非常多的邏輯處理。
慢慢地,復用性也會越來越差,可能需要經常重構,抽離代碼 以達到復用的程度。但往往業務的排期已經沒法抽開身去維護老代碼,那怎么辦呢?
hooks組件的分離
《重構2:改善既有代碼的設計》一文提到:把復雜的代碼塊分解為更小的單元,與好的命名一樣都很重要。
因此,我們需要在團隊內部達成共識,能夠產出一種固定的開發范式,能夠分離代碼,做到職責清晰,例如:A模塊專門處理View視圖組件,B模塊專門處理業務邏輯,C模塊專門維護ts類型types,D模塊專門維護各類常量constants,E模塊專門維護公用hooks邏輯,F模塊專門維護css modules等。
那么,在這前提之下,我們需要實現前端UI與業務邏輯分離,目前主流的有兩種方式,一種是純邏輯抽離出去,返回函數內部方法和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> }
這種方式沒什么太大問題,但這種代碼不內聚,沒法提供通用的邏輯處理,一旦業務發生變化,就會引發多處代碼的維護危機。
其次如果有很多業務團隊,那么就需要考慮如何規范化統一團隊內部寫法,如何支持更健壯的業務代碼。
UI與邏輯分離并不是最終的目的,最終的目的應該是形成一套易于維護,模塊職責劃分清晰,能夠形成固定開發模式,易于擴展,能夠規范化業務使用場景,且具備強壯生命力的方案。
如果這種方式可以實現的話,那么為何很少有人會這么干呢?原因可能在于大家的函數式組件的思維。
在hooks還沒誕生之前,大家普遍對于函數式組件的認知就是沒有state,所以當props是固定的,那么函數式組件每次渲染結果也都是一樣的,也就是相同的輸入總能得到相同的輸出。但現在hooks出現了,函數組件內部可以維護state了,相同的輸入并不一定能得到相同的輸出了。
此外,這種方式與可復用的hooks的區別又在哪里,如果兩種都使用hooks維護,又如何區分呢?
另外一種方式就是保留業務邏輯,但把UI組件抽離出去,這種方式更不推薦了。有點類似子組件,父子組件通信的既視感隨之襲來。
接下來,我們再來看下純hooks組件飽受大家詬病的一些問題:
純hooks組件的問題
1、useState 寫法難用,如果有很多state,需要一個個去維護,寫法不夠簡潔;當業務邏輯越來越復雜,往往會出現一個模塊幾十個useState需要維護的尷尬局面。
2、useReducer + context
的全局狀態難用,仍然需要定義很多action type
,還需要提供provider,使用useReducer跨組件共享狀態很麻煩
3、useCallback 用法不夠清晰,不知何時用何時不用,用法造成困惑
4、 生命周期需要引入useEffect,需要手動管理,且不夠語義化
5、基于hooks的業務組件,內部方法依然難以做到復用,應抽離出去單獨維護。
6、當使用useEffect模擬mounted事件時,處理異步請求函數時很麻煩。
7、當組件達到一定復雜度的時候,堆積到一起的代碼會變得越來越難以維護
8、React Hook的閉包陷阱問題
9、useState 調用updater更新后,無法同步獲取最新state值
10、useState updater無法實現細粒度更新對象的屬性值,不得不淺拷貝一份數據再進行覆蓋
hooks-view-model
想要寫出健壯的,長期可持續維護的代碼,就必須去理解這些在其他編程領域通用的設計模式、原則、范式。提高代碼質量,除了依賴開發自測和相關流程規范化外,也應有相關工具或統一的開發范式做約束。
對于純寫業務的人來說,沒有規范去強制約定,那么幾乎沒有人會這么處理業務邏輯與UI的關系,最終還是會寫到一起。這是hooks這種弱約束的弊端。
基于上述問題,我開發了基于react hooks的UI與業務邏輯分離的方案,內部基于useState hooks的updater 實現。可實現在class內部setState,然后在View組件中響應更新。基本解決了上述react hooks的十個“老大難”問題
hooks-view-model
是一種通過拆分UI視圖與業務邏輯的解決方案,可做到無需useReducer,無需redux等技術方案實現全局狀態更新而不會渲染無關組件。hooks-view-model
是集狀態管理,變量的存儲管理和數據的持久化管理于一體的解決方案。
詳情點擊??:https://github.com/hawx1993/h...
hooks-view-model
?主要用于分離UI與業務邏輯,可以解決 純hooks組件的問題,對比一下hooks-view-model的優勢:
hooks組件問題 | hooks-view-model |
---|---|
useState 寫法難用,如果有很多state,需要一個個去維護,寫法不夠簡潔 | 可通過對象形式更新與解構數據,寫法簡潔 |
useReducer + context的全局狀態難用,仍然需要定義很多action type,還需要提供provider,使用useReducer跨組件共享狀態很麻煩 | 全局狀態更新只需使用useGlobalStatehooks,用法簡單 |
生命周期需要引入useEffect,需要手動管理,且不夠語義化 | 提供mounted和unmounted 鉤子函數,可自動執行,語義化友好 |
基于hooks的業務組件,內部方法依然難以做到復用,應抽離出去單獨維護 | class 寫法可通過繼承 實現復用,還可以通過useVM引入其他viewModel進行復用,復用性高 |
當接收新的props,需要手動使用useEffect觀察props變化,沒有直接的鉤子可以自動觸發 | class 提供onPropsChanged?鉤子函數,可自動觸發執行 |
當組件達到一定復雜度的時候,堆積到一起的代碼會變得越來越難以維護 | UI與邏輯做到了很好的分離,代碼組織性強 |
React Hook的閉包陷阱問題 | 由于方法都提到class中去維護了,所以不存在此問題 |
useState 調用updater更新后,無法同步獲取最新state值 | 可通過調用getCurrentState 同步獲取最新值 |
調用updater無法實現細粒度更新對象屬性值,需淺拷貝對象后覆蓋 | 可通過updateImmerState實現細粒度更新 |
1、View:獲取數據并展示數據
// 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:管理狀態和處理數據
updateGlobalStateByKey
?和?updateCurrentState
?相當于在class中可以使用的setState方法,只不過需要保證class中的所有方法都是箭頭函數,否則會報錯
// AppViewModel.ts import StoreViewModel from 'hooks-view-model' class AppViewModel extends StoreViewModel { changeAddress = () => { this.updateCurrentState(this.props.address);// 相當于setState } } export { AppViewModel }
那么可能有很多人就疑惑了,明明react官方已經推崇函數式寫法了,為什么還要用class?
基于class的viewModel寫法與hooks有什么區別
誠然,hooks 可滿足UI與邏輯分離的需求,但抽離無法被公用的業務邏輯到hooks中是否有必要?與可復用的hooks 是否容易造成混淆?hooks存在的useCallback,useReducer,以及對副作用的使用等容易造成使用困惑的,以及對useState 使用上的麻煩是否可以有其他方法簡化?
其次,函數式組件的寫法也并非函數式編程,相同的輸入(props)并不會得到相同的輸出(內部的state或全局的state都可能對結果產生影響)。
而業務邏輯抽離到class中,依然是函數式組件。class相比于function 天然的具有可組織性,可擴展性(extends),和可維護性。
首先,業務邏輯是比較復雜的,Class 具備繼承能力,可實現viewModel與view都獲得來自父類的能力;
其次,class 能夠更好維護業務邏輯代碼,在class中寫業務邏輯,完全可以忽視react hooks自帶的各種hooks,諸如useRef,useCallback,useReducer,useState
等,寫起業務邏輯來更加純粹;
再者,hooks 也可以與viewModel共存,只需要在view中引入hooks,然后將返回值作為props,通過useVM傳給viewModel即可,兩者是共存的,并不是互斥的。
基于class的viewModel可以更好的維護業務邏輯代碼,可以使用裝飾器,public,private等關鍵字,顯示提高代碼可維護性和擴展能力。而可復用的hooks可以用來抽象業務邏輯實現副作用觀察和邏輯復用,兩者具有不同的心智模型。
配置生成項目模板文件
此外,我還在hooks-view-model內置了項目的模板文件,可一鍵生成所需模板文件和代碼,這樣便可以讓各個業務線的前端團隊始終保持一致的開發規范和風格。
可以真正做到成員B可以低成本介入項目A中,提高代碼的可維護性,可閱讀性。用法如下:
執行如下步驟,可一鍵生成模板文件
1、添加腳本命令
在package.json的scripts中添加如下腳本命令
scripts: { "generate": "plop --plopfile ./node_modules/hooks-view-model/generators/index.js" }
2、根目錄創建template.config.js
指明模板需要生成的相對路徑地址:
const dir_to_generate = './src/pages/'; module.exports = dir_to_generate;
執行完后,便會在指定的目錄下生成如下模板文件:
更好的debug能力
使用hooks,我們如果想知道當前的state值,我們需要一個個console出來,而基于hooks-view-model,我們只需要在控制臺輸入:globalStore,即可查看所有view對應的state,通過key區分。可大大提升debug能力。
原文鏈接:https://segmentfault.com/a/1190000042779477
相關推薦
- 2022-03-15 使用swagger-bootstrap-ui ,訪問的時候 404
- 2022-06-12 Python同步方法變為異步方法的小技巧分享_python
- 2022-06-17 go語言beego框架web開發語法筆記示例_Golang
- 2023-02-01 Bat腳本-Call,Start,直接調用,goto?四種方式調用批處理_DOS/BAT
- 2022-06-29 C語言超詳細講解指針的概念與使用_C 語言
- 2023-02-12 python中使用docx模塊處理word文檔_python
- 2022-07-18 SQL?Server中實現錯誤處理_MsSql
- 2022-09-30 關于react中useCallback的用法_React
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支