cover

一句話:小專案隨便寫都能動,但元件超過 50 個的時候,架構決定你是在寫 code 還是在還債。

先講結論

React 的架構問題歸納起來就兩件事:元件怎麼分狀態放哪裡。這篇不是 React 入門(如果你需要,先看 React Hooks),而是回答一個問題:你已經會寫 React 了,怎麼寫出不會讓三個月後的自己想翻桌的 React 應用?


元件分層:問自己三個問題

元件分層不是什麼高深的理論,就是問三個問題:

  • 這個元件換到另一個專案還能用嗎?→ UI 層(Button、Modal、Table)
  • 換到另一個頁面還能用嗎?→ Features 層(UserTable、UserForm)
  • 都不行?→ Pages 層(UserListPage)

就這樣。UI 層不知道業務邏輯,Features 層不知道 URL,Pages 層負責把它們組合起來。

你有沒有遇過一個 component 裡面同時 fetch 資料、處理表單、控制 modal、還寫了一堆 CSS?那就是沒有分層的症狀。我之前寫過一個 800 行的 component,重構的時候差點哭出來。

Container / Presentational 分離

Hooks 出來之後很多人說這個 pattern 過時了,但分離邏輯和呈現的思路永遠不過時:

// Container:負責邏輯
function UserListContainer() {
  const { users, loading, error } = useUsers();
  const handleDelete = useDeleteUser();
 
  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  return <UserList users={users} onDelete={handleDelete} />;
}
 
// Presentational:負責呈現
function UserList({ users, onDelete }) {
  return (
    <ul>
      {users.map(user => (
        <UserCard key={user.id} user={user} onDelete={onDelete} />
      ))}
    </ul>
  );
}

為什麼要這樣分?Presentational 元件可以用 Storybook 獨立開發和測試,同一份資料可以有 list / grid / table 三種呈現方式。邏輯改了不用動 UI,UI 改了不用動邏輯。

Compound Components

當一組元件需要共享隱含的狀態時——比如 Tabs、Accordion、Dropdown:

<Tabs defaultValue="profile">
  <Tabs.List>
    <Tabs.Trigger value="profile">Profile</Tabs.Trigger>
    <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="profile">Profile content</Tabs.Content>
  <Tabs.Content value="settings">Settings content</Tabs.Content>
</Tabs>

父元件透過 Context 共享狀態,子元件可以任意組合。使用者寫起來是宣告式的,不用自己管 active state。如果你用過 Radix UI 或 Headless UI,你已經在用 Compound Components 了。


狀態管理:先問「這個狀態住在哪裡最自然」

狀態管理的問題不是「該用什麼工具」,而是「這個狀態屬於誰」。

Local State——只有一個元件用。useState / useReducer,不要想太多。表單輸入、Modal 開關、tooltip 顯示,都是 local state。

Server State——來自 API。這個最容易搞錯。很多人把 API 回傳的資料放進 Redux / Context,然後手動管理 loading、error、refetch、cache invalidation… 拜託不要。

function UserList() {
  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => api.getUsers(),
  });
  // React Query 自動處理 loading、cache、background refetch、retry
  // 你不用寫任何一行狀態管理的 code
}

URL State——搜尋條件、分頁、篩選器。能放 URL 就放 URL,使用者可以分享連結、可以按上一頁。這些狀態放在 Redux 裡是浪費。

Global State——以上都不是的,才考慮全域狀態。2026 年了,真正需要全域管理的狀態其實很少:

  • 使用者登入狀態
  • 主題 / 語言偏好
  • 通知佇列
  • 多步驟表單的跨頁資料

就這些。如果你發現你的 Redux store 有 20 個 slice,我敢說其中 15 個應該是 React Query 或 URL state。2016 年全部塞 Redux 的風潮,真的害了不少人。


元件怎麼分、狀態放哪裡,搞定這兩件事,你的 React 應用就不會長成毛線球。下一篇講 Custom Hooks 設計、效能優化、和專案結構——也就是「骨架有了,怎麼讓肌肉長對地方」。

系列文章

延伸閱讀