一個簡潔、強大、可擴展的前端項目架構是什麼樣的?
大家好,我卡頌。
React技術棧的一大優勢在於 —— 社區繁榮,你業務中需要實現的功能基本都能找到對應的開源庫。
但繁榮也有不好的一面 —— 要實現同樣的功能,有太多選擇,到底選哪個?
本文要介紹一個 12.7k 的開源項目 —— Bulletproof React[1]
這個項目爲構建 「簡潔、強大、可擴展的前端項目架構」 的方方面面給出了建議。
Bulletproof React 是什麼
Bulletproof React與我們常見的腳手架(比如CRA)不同,後者的作用是 「根據模版創建一個新項目」。
而前者包含一個完整的React全棧論壇項目:
作者通過這個項目舉例,展示了與 「項目架構」 相關的 13 個方面的內容,比如:
-
文件目錄該如何組織
-
工程化配置有什麼推薦
-
寫業務組件時該怎麼規範
-
怎麼做狀態管理
-
API層如何設計 -
等等......
限於篇幅有限,本文介紹其中部分觀點。
不知道這些觀點你是否認同呢?
文件目錄如何組織
項目推薦如下目錄形式:
src
|
+-- assets # 靜態資源
|
+-- components # 公共組件
|
+-- config # 全局配置
|
+-- features # 特性
|
+-- hooks # 公用hooks
|
+-- lib # 二次導出的第三方庫
|
+-- providers # 應用中所有providers
|
+-- routes # 路由配置
|
+-- stores # 全局狀態stores
|
+-- test # 測試工具、mock服務器
|
+-- types # 全局類型文件
|
+-- utils # 通用工具函數
其中,features目錄與components目錄的區別在於:
components存放全局公用的組件,而features存放 「業務相關特性」。
比如我要開發 「評論」模塊,「評論」 作爲一個特性,與他相關的所有內容都存在於features/comments目錄下。
「評論」 模塊中需要輸入框,輸入框這個通用組件來自於components目錄。
所有 「特性相關」 的內容都會收斂到features目錄下,具體包括:
src/features/xxx-feature
|
+-- api # 與特性相關的請求
|
+-- assets # 與特性相關的靜態資源
|
+-- components # 與特性相關的組件
|
+-- hooks # 與特性相關的hooks
|
+-- routes # 與特性相關的路由
|
+-- stores # 與特性相關的狀態stores
|
+-- types # 與特性相關的類型申明
|
+-- utils # 與特性相關的工具函數
|
+-- index.ts # 入口
特性導出的所有內容只能通過統一的入口調用,比如:
import { CommentBar } from "@/features/comments"
而不是:
import { CommentBar } from "@/features/comments/components/CommentBar
這可以通過配置ESLint實現:
{
rules: {
'no-restricted-imports': [
'error',
{
patterns: ['@/features/*/*'],
},
],
// ...其他配置
}
}
相比於將 「特性相關的內容」 都以 「扁平的形式」 存放在全局目錄下(比如將特性的 hooks 存放在全局 hooks 目錄),以features目錄作爲 「相關代碼的集合」 能夠有效防止項目體積增大後代碼組織混亂的情況。
怎麼做狀態管理
項目中並不是所有狀態都需要保存在 「中心化的 store」 中,需要根據狀態類型區別對待。
組件狀態
對於組件的局部狀態,如果只有組件自身以及他的子孫組件需要這部分狀態,那麼可以用useState或useReducer保存他們。
應用狀態
與應用交互相關的狀態,比如 「打開彈窗」、「通知」、「改變黑夜模式」 等,應該遵循 「將狀態儘可能靠近使用他的組件」 的原則,不要什麼狀態都定義爲 「全局狀態」。
以Bulletproof React中的示例項目舉例,首先定義 「通知相關的狀態」:
// bulletproof-react/src/stores/notifications.ts
export const useNotificationStore = create<NotificationsStore>((set) => ({
notifications: [],
addNotification: (notification) =>
set((state) => ({
notifications: [...state.notifications, { id: nanoid(), ...notification }],
})),
dismissNotification: (id) =>
set((state) => ({
notifications: state.notifications.filter((notification) => notification.id !== id),
})),
}));
再在任何使用 「通知相關的狀態」 的地方引用useNotificationStore,比如:
// bulletproof-react/src/components/Notifications/Notifications.tsx
import { useNotificationStore } from '@/stores/notifications';
import { Notification } from './Notification';
export const Notifications = () => {
const { notifications, dismissNotification } = useNotificationStore();
return (
<div
>
{notifications.map((notification) => (
<Notification
key={notification.id}
notification={notification}
onDismiss={dismissNotification}
/>
))}
</div>
);
};
這裏使用的狀態管理工具是zustand,除此之外還有很多可選方案:
-
context+hooks -
redux+redux toolkit -
mobx -
constate -
jotai -
recoil -
xstate
這些方案各有特點,但他們都是爲了處理**「應用狀態」**。
服務端緩存狀態
對於從服務端請求而來,緩存在前端的數據,雖然可以用上述處理 「應用狀態」 的工具解決,但 「服務端緩存狀態」 相比於 「應用狀態」,還涉及到 「緩存失效」、「序列化數據」 等問題。
所以最好用專門的工具處理,比如:
-
react-query - REST+GraphQL -
swr - REST+GraphQL -
apollo client-GraphQL -
urql-GraphQl
表單狀態
表單數據需要區分 「受控」 與 「非受控」,表單本身還有很多邏輯需要處理(比如 「表單校驗」 ),所以也推薦用專門的庫處理這部分狀態,比如:
-
React Hook Form -
Formik -
React Final Form
URL 狀態
URL狀態包括:
-
url params(/app/${dynamicParam}) -
query params(/app?dynamicParam=1)
這部分狀態通常是路由庫處理,比如react-router-dom。
總結
本文節選了部分Bulletproof React中推薦的方案,有沒有讓你認可的觀點呢?
參考資料
[1] Bulletproof React: https://github.com/alan2207/bulletproof-react
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/vctWciAp1-AMtiMMUOz_xg