首頁 > 軟體

React構建簡潔強大可延伸的前端專案架構

2022-08-05 18:00:04

引言 

React技術棧的一大優勢在於 —— 社群繁榮,你業務中需要實現的功能基本都能找到對應的開源庫。

但繁榮也有不好的一面 —— 要實現同樣的功能,有太多選擇,到底選哪個?

本文要介紹一個12.7k的開源專案 —— Bulletproof React

這個專案為構建簡潔、強大、可延伸的前端專案架構的方方面面給出了建議。

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中,需要根據狀態型別區別對待。

元件狀態

對於元件的區域性狀態,如果只有元件自身以及他的子孫元件需要這部分狀態,那麼可以用useStateuseReducer儲存他們。

應用狀態

與應用互動相關的狀態,比如開啟彈窗、通知、改變黑夜模式等,應該遵循將狀態儘可能靠近使用他的元件的原則,不要什麼狀態都定義為全域性狀態。

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中推薦的方案,有沒有讓你認可的觀點呢?

以上就是React構建簡潔強大可延伸的前端專案架構的詳細內容,更多關於React前端專案架構的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com