---
# System prepended metadata

title: React 元件基本結構
tags: [note]

---

# React 元件基本結構
Elantris

[TOC]

---

## 前言

使用現代前端框架撰寫資訊系統時注重模組化開發，可以方便拆解細分每個流程步驟與各自功能，其中最小的組成單位被稱為元件。原則上一個元件要能表現單一功能且能重複利用，後續實作只要根據商業需求將這些元件組合起來就能實現完整操作流程。以下介紹我在設計 React 元件時從結構上會注意的一些重點。

## 組成結構

元件可以根據功能與形式粗略區分為三種層級：

- 元件（Component）
  具有簡單功能的最小基本單位
- 群組（Group）
  - 列表清單
    相同類型的元件集合，最常見需求為清單列表、格狀排列、輪播等形式
  - 一般群組
    不同類型的元件組合，要完整表達一個有意義的功能通常都會需要由複數個不同性質的元件組成，例如搜尋輸入框配搜尋按鈕、按鈕配下拉式選單等
  - 複合區塊
    不同類型、不同層級的元件複雜組合，用來表達複雜的功能或一連串的流程，通常只是實現商業邏輯的一次性元件、只出現在特定頁面、重複使用的機會較低
- 頁面（Page）
  在一個畫面中會同時載入所有元件的集合體，普遍來說一個系統的功能會以頁面為單位來呈現，頁面元件著重在狀態管理以及對應的顯示元件組合

現行 React 大多都是使用 functional component 以及 hook，即使是不同類型的元件在結構上仍然有差不多的組成要素：

- 元件定義（Definition）
  - name
  - props
- 元件內容（Content）
  - context
  - state
  - event handler
  - effect
  - rendering

### Name

元件命名的最高原則是需要它的時候能夠快速的找到它，每個團隊都有習慣的命名方式，找到一個大家都能接受的規則達成共識即可。

我喜歡的元件命名方式大致上分為三個部分：<==Feature==**Method**==Type== />

- Feature：產品功能
  從商業邏輯的角度來看這個元件主要是屬於哪一個系統功能的範疇
- Method：實際作用
  從程式邏輯的角度描述這個元件實際的功能
- Type：元件類型
  從外觀上來看這個元件實際在網頁上的實際結構

舉例來說：

1. ==Notification==**ShowMore**==Button==
   - Notification：是指組成「通知」功能的其中一個元件
   - ShowMore：代表這個元件可以用來「顯示更多」
   - Button：代表這個元件在網頁上就是一個「按鈕」
2. ==Order==**Detail**==Modal==
   - Order：訂單功能
   - Detail：詳細資料
   - Modal：蓋板浮動視窗

如此一來我在撰寫新的頁面、新的功能時都能快速替每個元件根據各自的定位找到適合的名稱，日後在維護系統時也可以從在哪個頁面底下、負責做什麼事、長什麼樣子精準地找到對應的元件。

### Props

React 傳遞資料的方式是由上而下資料流（one-way data flow），透過 props 將參數從父元件傳給子元件。其中如果需要由下而上去改變父元件的資料時，會特別用事件處理的形式將 callback 函數傳下去，讓子元件將特定資料帶出來。

例如表單元件輸入完後要把資料傳出來：

```tsx
<PriceInput
  defaultValue={100}
  onChange={(newValue) => {
    // handle new value
  }}
/>
```

### Context

接下來進到元件內部的第一層，Context API 讓資料可以跨元件的存取，只要複數個元件屬於同一個 Provider 底下就不必受限於父傳子的規則，當一個參數需要水平傳遞或跨越四層以上的元件時就適合包裝成 context。常見的 context 類型包含：主題樣式、身分權限、API Client 等，隨時隨地都有元件會用到的資料類型。

### State

使用現代前端框架的重點就是掌握元件狀態變化與渲染顯示之間的關係，而狀態管理本身就是一門值得探討的學問，從古至今前端開發的生態系催生出許多狀態管理的方法與套件。但既然這是一個牽扯到價值觀的選擇問題，我認為一樣只要參與開發的團隊成員達成共識就好。

從最基本的原則來看 state 本身應該是貼近元件本身越簡單越好，一個行為改變一個狀態、一個狀態綁定一個顯示規則，如果一個元件的狀態規則複雜到需要製作圖表才能釐清的話，通常我會建議將這個元件拆解成更細小的元件，透過更多元件層級來降低每一層需要處理的狀態複雜度。

### Event Handler

事件處理通常會包含建立 ref 以及事件監聽，透過 reference hook 綁定到渲染後的 html 元素上即可存取瀏覽器原生支援的 API，再定義事件監聽處理跟使用者互動有關的行為流程。

### Effect

React 在渲染時如果出現需要處理 side effect 的情境時會使用 useEffect，相比早期 class component 要根據 Life Cycle 中每一個特定時間點定義事件監聽，functional component 搭配 hook 的時代簡化到只需要處理 render 以及 unmount 兩個時機點就能應付大部分的使用情境。

### Rendering

最後一個區塊是 functional component 的 return 值，也就是實際會渲染到網頁上的 JSX 部分。最常用到的技巧是條件渲染（conditional rendering），根據 props、state 等數值變化決定要渲染的內容，或是改變元素的 className 影響 CSS 的行為。

## 總結

撰寫元件時我會從影響範圍大到小的順序排列上述這些結構組成：

```tsx
const SkillDetailTab: React.FC<{
  id: string // Props
}> = ({
  id
}) => {
  const theme = useTheme() // 第三方套件提供的 Context
  const contextValue = useConext(MyContext) // 自己寫的 Context
  
  const { data } = useMyDataHook() // Custom Data Hook
  const [isActive, setIsActive] = useState(false) // State
  
  const ref = useRef() // Reference
  const onClick = (e: Event) => {} // Event Handler
  
  useEffect(() => {}, []) // Effect

  // Rendering
  if (isActive) {
    return <></>
  }

  return <></>
}
```
