跳到主要內容

FE Day 13 元件設計原則:組合、抽象與邊界

FE Day 13 元件設計原則:組合、抽象與邊界

執行需求:CPU 可跑。今天是「前端開發實戰:React 與 Next.js 全套」系列的第十三篇,正式進入「核心」章節的中段。Day 5 到 Day 12 我們把元件、props、state、事件、渲染模型、樣式、表單、資料取得都學會了,能寫出會動的 React 元件。但「會動」跟「好維護」是兩件事——同樣的預約系統,一個人寫的版本三個月後自己看不懂,另一個人寫的版本三個月後還能輕鬆加新功能。差別就在「元件設計」。今天要回答五個核心問題:什麼是組合(composition)?什麼時候該抽 Hook?什麼時候該抽元件?容器型與展示型怎麼分?React 19 的 memoization 工具怎麼用?讀完之後,你應該能寫出「好維護、好測試、好組合」的元件,並在遇到重複邏輯時正確判斷「該抽到哪」。整篇閱讀時間約四十鐘,動手做約三十五分鐘。

引言

後端工程師第一次寫 React,常會陷入「把元件寫成大函式」的陷阱:把所有邏輯塞在一個元件裡,檔案越長越長,最後變成 500 行的怪物。後端寫過 OOP 的人會本能地想「抽 class」,但在 React 函式元件的世界裡,「抽 class」這條路被 Hooks 取代了。新的設計哲學是「組合」(composition)——把元件想成樂高積木,每塊負責一件事,可以自由組合。

組合這個觀念的歷史可以追溯到函式程式設計:寫小函式、用大函式組合小函式。React 把同樣的觀念搬到 UI 元件:寫小元件、用大元件組合小元件。React 19 的設計團隊特別強調這一點——官方文件把「composition vs inheritance」放在元件章節的第一頁,意思是「組合」是 React 的核心哲學,不是選項。

今天的目標有四個:第一,理解「容器型 vs 展示型元件」的差別,學會分層;第二,掌握「複合元件」(compound components)的模式,學會設計彈性的 API;第三,理解「render props / children as function」的彈性設計;第四,認識 React 19 的三個 memoization 工具(React.memo、useMemo、useCallback)的正確使用時機。最後我們會用一個完整的「預約篩選面板」把這些觀念串起來,展示「小而專注的元件」如何組合出強大且好維護的 UI。

單一職責原則:把大元件拆成小元件

單一職責原則(Single Responsibility Principle)講的是「一個元件只做一件事」。這個觀念聽起來抽象,實際上很具體:當一個元件同時負責「抓資料」、「管理 state」、「渲染 UI」、「處理事件」,它就有 4 個職責,應該拆成 4 個元件(或 Hook)。

判斷「該不該拆」有一個簡單的原則:「這個元件的 props 變了,需要重新 render 嗎?如果是獨立的,那它就應該是獨立元件。」例如一個「預約卡片」元件,它內部有「標題」、「狀態標籤」、「操作按鈕」、「時間資訊」四個區塊。如果未來標題要加 hover 效果,狀態標籤要根據使用者角色變色——這兩個變化是獨立的,應該拆成 Header、StatusBadge、ActionButtons、TimeInfo 四個子元件。

// src/features/booking/BookingCard.tsx
// 拆分後的 BookingCard:把每個區塊抽成獨立元件
import type { Booking } from "../../types/booking";
import { BookingHeader } from "./BookingHeader";
import { BookingStatusBadge } from "./BookingStatusBadge";
import { BookingTimeInfo } from "./BookingTimeInfo";
import { BookingActions } from "./BookingActions";

type BookingCardProps = {
  booking: Booking;
  onCancel?: (id: number) => void;
  canEdit?: boolean;
};

export function BookingCard({ booking, onCancel, canEdit }: BookingCardProps) {
  return (
    <article className="rounded-lg border p-4 shadow-sm">
      <BookingHeader customerName={booking.customerName} id={booking.id} />
      <BookingStatusBadge status={booking.status} />
      <BookingTimeInfo startAt={booking.startAt} duration={booking.duration} />
      {canEdit && onCancel && (
        <BookingActions onCancel={() => onCancel(booking.id)} />
      )}
    </article>
  );
}

這支 BookingCard 把每個區塊拆成獨立元件,主元件只剩「組裝」這件事。帶來的好處有四個:第一,每個子元件可以單獨測試,不需要 render 整張卡片;第二,狀態變化只觸發對應子元件的 re-render(例如狀態從「待確認」變「已確認」只有 StatusBadge 重繪);第三,未來改某個區塊的 UI 不會影響其他區塊;第四,storybook 可以逐個展示每個子元件。

另一個拆分的訊號是「程式碼長度」。當一個元件超過 200 行,它幾乎一定有重複邏輯或太多職責。Day 16 會展開專案結構與檔案組織,到時候會談「feature folder」的拆分方式:每個功能(例如 booking)有自己的資料夾,內含 components、hooks、types、utils 子目錄。

容器型 vs 展示型元件

React 元件最常見的分類是「容器型」(Container)與「展示型」(Presentational)。前者負責「資料邏輯」(抓 API、管 state、處理事件),後者負責「視覺呈現」(純接收 props、純渲染)。這個分類來自 Redux 時代的最佳實踐,雖然 React 19 之後 Hooks 模糊了邊界,但概念仍然好用。

// src/features/booking/BookingFilterPanel.tsx
// 容器型元件:管資料邏輯
import { useState } from "react";
import type { Booking } from "../../types/booking";
import { BookingFilterView } from "./BookingFilterView";

type Props = { bookings: Booking[] };

export function BookingFilterPanel({ bookings }: Props) {
  const [keyword, setKeyword] = useState("");
  const [status, setStatus] = useState<string>("all");

  const filtered = bookings.filter((b) => {
    if (keyword && !b.customerName.includes(keyword)) return false;
    if (status !== "all" && b.status !== status) return false;
    return true;
  });

  // 把邏輯(state、計算)與展示(JSX)分離
  return (
    <BookingFilterView
      keyword={keyword}
      status={status}
      filtered={filtered}
      total={bookings.length}
      onKeywordChange={setKeyword}
      onStatusChange={setStatus}
    />
  );
}
// src/features/booking/BookingFilterView.tsx
// 展示型元件:純 props,純 JSX
import type { Booking } from "../../types/booking";

type ViewProps = {
  keyword: string;
  status: string;
  filtered: Booking[];
  total: number;
  onKeywordChange: (value: string) => void;
  onStatusChange: (value: string) => void;
};

export function BookingFilterView({
  keyword, status, filtered, total,
  onKeywordChange, onStatusChange,
}: ViewProps) {
  return (
    <div className="space-y-4">
      <input
        type="search"
        value={keyword}
        onChange={(e) => onKeywordChange(e.target.value)}
        placeholder="搜尋姓名或電話"
        className="w-full rounded border px-3 py-2"
      />
      <select
        value={status}
        onChange={(e) => onStatusChange(e.target.value)}
        className="rounded border px-3 py-2"
      >
        <option value="all">全部</option>
        <option value="pending">待確認</option>
        <option value="confirmed">已確認</option>
        <option value="cancelled">已取消</option>
        <option value="completed">已完成</option>
      </select>
      <p className="text-sm text-slate-500">
        共 {filtered.length} 筆(總共 {total} 筆)
      </p>
      <ul className="divide-y rounded border">
        {filtered.map((b) => (
          <li key={b.id} className="p-3">
            <p className="font-medium">{b.customerName}</p>
            <p className="text-sm text-slate-600">{b.startAt}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

這組範例展示容器/展示的典型分工:BookingFilterPanel 管 state、計算過濾邏輯、把 props 傳下去;BookingFilterView 只負責「拿到 props 就渲染」。好處有三個:第一,展示型元件可以在 Storybook 展示,不用準備真實資料;第二,展示型元件容易測試,只要傳 props 就好;第三,未來換資料來源(例如從 API 改成 WebSocket)只改容器,展示型元件完全不受影響。

要注意的是,「容器/展示」的界線不是死的。一個中型元件常常同時有資料邏輯和視覺,這時候不必硬拆。等邏輯長到 100 行以上、視覺變複雜到需要拆檔時再拆就好。過度拆分(每個 5 行的元件)會讓程式碼難以追蹤,反而是另一種災難。

複合元件:讓 API 更語意化

複合元件(compound components)是一種「把多個相關的元件綁在一起」的設計模式。最經典的案例是 HTML 的 <select><option>:select 跟 option 是父子元件,但開發者不會單獨用 option(沒有 select 的 option 沒有意義)。React 的 Tabs、Menu、Accordion 都是這種模式。

複合元件的實現有多種方式,最簡單的是「父元件 export 多個子元件」。呼叫端寫起來語意化、組合靈活:

// src/components/Tabs.tsx
// 複合元件:Tabs + Tabs.List + Tabs.Tab + Tabs.Panel
import { useState } from "react";
import type { ReactNode } from "react";

type TabsContext = {
  activeId: string;
  setActiveId: (id: string) => void;
};

// 用 Context 讓子元件共享 activeId
import { createContext, useContext } from "react";
const TabsContext = createContext<TabsContext | null>(null);

function useTabs() {
  const ctx = useContext(TabsContext);
  if (!ctx) throw new Error("Tabs 子元件必須放在 Tabs 內");
  return ctx;
}

// 主元件:管理 state、提供 Context
function TabsRoot({
  defaultId,
  children,
}: {
  defaultId: string;
  children: ReactNode;
}) {
  const [activeId, setActiveId] = useState(defaultId);
  return (
    <TabsContext.Provider value={{ activeId, setActiveId }}>
      {children}
    </TabsContext.Provider>
  );
}

// 子元件:透過 Context 知道當前 active
function TabsList({ children }: { children: ReactNode }) {
  return <div role="tablist" className="flex border-b">{children}</div>;
}

function TabsTab({ id, children }: { id: string; children: ReactNode }) {
  const { activeId, setActiveId } = useTabs();
  const isActive = activeId === id;
  return (
    <button
      type="button"
      role="tab"
      aria-selected={isActive}
      onClick={() => setActiveId(id)}
      className={[
        "px-4 py-2 text-sm",
        isActive ? "border-b-2 border-slate-900 font-semibold" : "text-slate-500",
      ].join(" ")}
    >
      {children}
    </button>
  );
}

function TabsPanel({ id, children }: { id: string; children: ReactNode }) {
  const { activeId } = useTabs();
  if (activeId !== id) return null;
  return <div role="tabpanel" className="p-4">{children}</div>;
}

// 組合:把多個子元件掛到 Tabs 主元件上
export const Tabs = Object.assign(TabsRoot, {
  List: TabsList,
  Tab: TabsTab,
  Panel: TabsPanel,
});

這組 Tabs 展示了完整的複合元件模式。呼叫端寫起來非常語意化:

// 用法
<Tabs defaultId="info">
  <Tabs.List>
    <Tabs.Tab id="info">資訊</Tabs.Tab>
    <Tabs.Tab id="stats">統計</Tabs.Tab>
    <Tabs.Tab id="settings">設定</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel id="info">預約總覽</Tabs.Panel>
  <Tabs.Panel id="stats">12 筆待確認</Tabs.Panel>
  <Tabs.Panel id="settings">通知偏好</Tabs.Panel>
</Tabs>

這種寫法跟 HTML 原生的 <select><option> 一致,使用者一看就懂。背後的實作關鍵有兩個:第一,用 Context 把 activeId 從主元件傳到子元件,子元件不需要父元件層層傳 props;第二,用 Object.assign 把多個子元件「掛」到主元件上,讓使用者可以用 Tabs.Tab 這樣的命名空間寫法。

實務上 Radix UI、shadcn/ui、Headless UI 等主流元件庫幾乎都採複合元件模式。學會這個模式,你就能看懂所有主流元件庫的原始碼,也能自己設計語意化的 API。Day 32 設計系統章節會大量用到這個觀念。

Children as function:彈性的渲染策略

另一個元件設計的彈性模式是「children as function」(或稱 render props)。這個模式讓父元件把「決定渲染什麼」的權力交給呼叫端,而不是寫死。

// src/components/DataLoader.tsx
// Children as function:把渲染權交給呼叫端
import type { ReactNode } from "react";

type DataLoaderProps<T> = {
  data: T | null;
  loading: boolean;
  error: Error | null;
  children: (state: {
    data: T | null;
    loading: boolean;
    error: Error | null;
  }) => ReactNode;
};

export function DataLoader<T>({ data, loading, error, children }: DataLoaderProps<T>) {
  return <>{children({ data, loading, error })}</>;
}

呼叫端可以根據 loading、error、data 三種狀態決定怎麼渲染:

// 用法:把渲染邏輯留給呼叫端
<DataLoader<Booking[]> data={bookings} loading={isLoading} error={error}>
  {({ data, loading, error }) => {
    if (loading) return <p>載入中…</p>;
    if (error) return <p className="text-red-600">載入失敗:{error.message}</p>;
    if (!data || data.length === 0) return <p>目前沒有預約</p>;
    return <ul>{data.map((b) => <li key={b.id}>{b.customerName}</li>)}</ul>;
  }}
</DataLoader>

這個模式的優點是「父元件只負責資料,子元件自己決定怎麼顯示」。缺點是呼叫端要寫較多程式碼——所以實務上常用在「資料載入層」這種需要給呼叫端完全自由的情境。一般 UI 元件用 props 就夠了,不需要 render props。React 19 的 useActionState 與 Suspense 也減少了 render props 的使用場景。

另一個延伸是「slots」模式:用多個具名 children(不只 children 這一個 prop)來組成元件。例如 Modal 元件需要 title、body、footer 三個區域,可以這樣設計:

// src/components/Modal.tsx
// Slots 模式:用多個 props 接收 JSX
import type { ReactNode } from "react";

type ModalProps = {
  title: ReactNode;
  body: ReactNode;
  footer?: ReactNode;
  onClose: () => void;
};

export function Modal({ title, body, footer, onClose }: ModalProps) {
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
      <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
        <div className="mb-4 flex items-center justify-between">
          <h2 className="text-lg font-semibold">{title}</h2>
          <button type="button" onClick={onClose} aria-label="關閉" className="text-slate-500 hover:text-slate-900">
            ×
          </button>
        </div>
        <div className="mb-4">{body}</div>
        {footer && <div className="flex justify-end gap-2">{footer}</div>}
      </div>
    </div>
  );
}

這個 Modal 展示 slots 模式:title、body、footer 都是 ReactNode 類型,呼叫端傳什麼就顯示什麼:

// 用 Modal:title、body、footer 三個 slots
<Modal
  title=<span>確認刪除</span>
  body=<p>這筆預約刪除後無法復原,確定嗎?</p>
  footer=<>
    <button type="button" onClick={onClose}>取消</button>
    <button type="button" onClick={handleDelete}>確定刪除</button>
  </>
/>

Slots 模式讓父元件保持視覺一致性,但把內容決策權交給呼叫端。這比 children-as-function 更直觀(不需要寫函式),適合用在「視覺框架固定、內容客製化」的情境——例如 Modal、Drawer、Card、Section 都是這種模式的好應用。

完整實作:可重用的預約面板

把今天學的觀念整合起來:寫一個「可重用預約面板」元件,展示容器/展示、複合元件、單一職責的綜合運用。

// src/features/booking/BookingPanel.tsx
// 容器:管資料邏輯
import { useMemo, useState } from "react";
import type { Booking } from "../../types/booking";
import { BookingPanelView } from "./BookingPanelView";

type Props = { bookings: Booking[]; defaultStatus?: string };

export function BookingPanel({ bookings, defaultStatus = "all" }: Props) {
  const [keyword, setKeyword] = useState("");
  const [status, setStatus] = useState(defaultStatus);

  // 用 useMemo 記住過濾結果(避免每次 render 重算)
  const filtered = useMemo(() => {
    return bookings.filter((b) => {
      if (keyword && !b.customerName.includes(keyword)) return false;
      if (status !== "all" && b.status !== status) return false;
      return true;
    });
  }, [bookings, keyword, status]);

  return (
    <BookingPanelView
      keyword={keyword}
      status={status}
      filtered={filtered}
      total={bookings.length}
      onKeywordChange={setKeyword}
      onStatusChange={setStatus}
    />
  );
}
// src/features/booking/BookingPanelView.tsx
// 展示:純 props、純 JSX
import type { Booking } from "../../types/booking";

type ViewProps = {
  keyword: string;
  status: string;
  filtered: Booking[];
  total: number;
  onKeywordChange: (v: string) => void;
  onStatusChange: (v: string) => void;
};

export function BookingPanelView({
  keyword, status, filtered, total,
  onKeywordChange, onStatusChange,
}: ViewProps) {
  return (
    <section className="space-y-4">
      <header className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
        <input
          type="search"
          value={keyword}
          onChange={(e) => onKeywordChange(e.target.value)}
          placeholder="搜尋姓名或電話"
          className="rounded border px-3 py-2 md:w-72"
        />
        <select
          value={status}
          onChange={(e) => onStatusChange(e.target.value)}
          className="rounded border px-3 py-2"
        >
          <option value="all">全部</option>
          <option value="pending">待確認</option>
          <option value="confirmed">已確認</option>
          <option value="cancelled">已取消</option>
          <option value="completed">已完成</option>
        </select>
      </header>
      <p className="text-sm text-slate-500">
        共 {filtered.length} 筆(總共 {total} 筆)
      </p>
      <ul className="divide-y rounded border">
        {filtered.map((b) => (
          <li key={b.id} className="p-3">
            <p className="font-medium">{b.customerName}</p>
            <p className="text-sm text-slate-600">{b.startAt}</p>
          </li>
        ))}
      </ul>
    </section>
  );
}

這組元件展示了今天學的所有觀念:第一,BookingPanel 是容器,BookingPanelView 是展示,兩者職責分離;第二,用 useMemo 記住 filtered,避免每次 render 重算(這是 React 19 的重要最佳化工具);第三,把 keyword 與 status 拆成獨立 state,UI 改變時只有對應子元件 re-render;第四,呼叫端只需傳 bookings 與 defaultStatus,整個面板就能運作。整套程式碼約 80 行,比 500 行的單檔怪物好維護太多。

元件 vs 自訂 Hook:邏輯該放哪

談元件設計不能跳過「邏輯該放元件還是 Hook」。這個問題沒有標準答案,但有一套判斷原則:「邏輯有沒有產生 UI?」。如果有,元件;如果沒有,Hook。例如「篩選 bookings」這個邏輯——它計算新的陣列、不產生 UI,就應該抽成 Hook(如 useBookingFilter);「渲染篩選後的清單」是 UI 工作,留在元件裡。

判斷流程可以整理成三個問題:第一,這段邏輯有回傳 JSX 嗎?有 → 元件;沒有 → Hook 候選人。第二,這段邏輯會被多個元件共用嗎?是 → 抽 Hook;否則留在原元件。第三,這段邏輯需要有自己的 state 或 effect 嗎?是 → 抽 Hook;否則可以是純函式。當三個問題的答案明確指向 Hook,就抽 Hook;指向元件,就抽元件;都模糊時先留在原處,等重複出現再決定。

React 19 對這個問題的官方建議是「先試 Hook」。原因是 Hook 比元件更易組合——Hook 回傳的值可以傳給任何元件;元件的回傳值(JSX)就只能用在 render 階段。所以當一段邏輯「可能產生 JSX、也可能純資料」時,先試 Hook,把 JSX 的部分留給呼叫端的元件去組合。今天的 BookingPanelView 之所以不是 Hook,是因為它「純粹是 JSX」——沒邏輯、沒 state、沒 effect,這就是展示型元件的典型形狀。

另一個常見的設計決定是「Hook 要不要回傳一個完整的物件」。例如 useBookingFilter 可以回傳 { keyword, status, setKeyword, setStatus, filtered },也可以回傳多個值 [keyword, status, setKeyword, setStatus, filtered]。React 19 的社群偏好回傳物件(語意化、可擴充),但物件解構會失去 React 的 fast-refresh 支援。實務上小型 Hook 用 array 比較方便,大型 Hook 用 object 比較語意化。

常見錯誤與踩雷

元件設計最常踩的雷有四個。第一個是「過早抽象」。看到兩段相似的程式碼就急著抽共用元件,結果抽象錯了,後續改需求的時候反而要拆掉重來。建議「三振原則」:看到重複三次以上再考慮抽象,第二次出現先接受重複。

第二個是「props drilling 太深」。當 A 元件傳 props 給 B、B 再傳給 C、C 再傳給 D,越來越深時,維護成本直線上升。修法是用 Context(Day 11)把這層提升跳過。但 Context 不是萬靈丹——它會讓元件「隱性依賴」於某個 Provider,測試時要包 Provider 才能跑。

第三個是「忘了 React.memo 的合理使用情境」。React.memo 是「讓 props 沒變就不 re-render」的工具,但「props 沒變」這個判斷對**物件、陣列、函式**會失敗——因為每次 render 都會建立新的 reference。所以「React.memo(MyComponent) + 每次 render 都傳新的 array prop」會讓 memo 完全失效。修法是搭配 useMemo(記住 array)與 useCallback(記住函式)。但這也要看情境:當元件本身很便宜(render 不到 1ms),加 memo 反而是浪費——React 還要花時間「判斷 props 沒變」。實務原則:「先不加 memo,等真的有效能問題再加」。

第四個是「元件命名不一致」。同樣性質的元件,有的叫 XList、有的叫 XItems、有的叫 Xs——維護時找不到。建議團隊約定命名規範:「展示清單用 List、單一條目用 Item、表單欄位用 Field、按鈕用 Button」。TypeScript 可以搭配 type-level 的命名約束來強化這個慣例。

效能與實務提醒

React 19 的 memoization 工具用錯反而會拖慢效能。第一個提醒是「useMemo 的成本」。useMemo 本身有記憶與比對的成本,只有「計算昂貴」或「傳給 memo 子元件的 reference 必須穩定」這兩種情境才值得用。純字串相加、陣列 filter 在小型資料下完全不需要 useMemo。

第二個是「React.memo 的對等比較」。React.memo 預設用 Object.is 比較 props,但對於物件型 props 它會逐層比較(shallow equal)。當 props 內含物件或函式,必須搭配 useMemo/useCallback 才能真的穩定。第三個是「useCallback 的常見誤用」。把每個事件處理函式都包 useCallback 是過度最佳化——只有當這個函式會傳給 memo 子元件時才需要 useCallback,否則不必。

另一個延伸主題是「狀態的拆分」。當一個元件管太多 state,每次某個 state 變動都會讓整個元件 re-render。正確的拆分原則是「依變動頻率拆」:每 1 秒變動的 state 跟每分鐘變動的 state 分開放;表單欄位之間通常獨立變動也應該拆。今天寫的 BookingPanel 就是好例子:keyword(每次按鍵變)與 status(每次選擇變)拆成兩個 useState,UI 上對應的 input 與 select 子元件只訂閱自己的 state。

最後一個提醒是「元件的單元測試」。當元件拆得當(容器/展示分離、單一職責),測試就簡單:展示型元件傳 props 就好、容器型元件可以 mock 子元件。Day 15 的 Vitest 章節會用今天這些元件當範例,把測試寫法一次講清楚。

小結

今天我們把元件設計的觀念拆開來看:單一職責原則、容器/展示分離、複合元件(Tabs + Tabs.List + Tabs.Tab + Tabs.Panel)、render props 與 slots 模式。最後用一個完整的 BookingPanel + BookingPanelView 把所有觀念串起來。明天 Day 14 進入資料取得:fetch、useEffect 怎麼打 API、TanStack Query 怎麼做快取、loading 與 error 狀態怎麼設計。Day 14 結束時,你的前端就能跟真實的後端 API 串接了。

結語

明天,我們會把資料取得一次學完:原生 fetch + useEffect 的基本寫法、TanStack Query(或 SWR)怎麼處理快取與重試、loading / error / empty 三種狀態怎麼設計、stale time 與 gc time 的差別。Day 14 是「前端從純 UI 跨到資料驅動」的關鍵轉折。接著 Day 15 進入測試——把今天寫的元件用 Vitest 與 React Testing Library 跑起來,驗證重構沒有破壞既有行為。

延伸資源

  • React 官方〈Thinking in React〉:<code>https://react.dev/learn/thinking-in-react</code>
  • React 官方〈Passing Props to a Component〉:<code>https://react.dev/learn/passing-props-to-a-component</code>
  • React 官方〈Reusing Logic with Custom Hooks〉:<code>https://react.dev/learn/reusing-logic-with-custom-hooks</code>
  • Kent C. Dodds〈Advanced React Patterns〉:<code>https://kentcdodds.com/blog/advanced-react-patterns</code>
  • shadcn/ui 元件原始碼(複合元件最佳範例集):<code>https://ui.shadcn.com/</code>

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...