跳到主要內容

FE Day 11 狀態管理:Context、提升與拆分

FE Day 11 狀態管理:Context、提升與拆分

執行需求:CPU 可跑。今天是系列的第十一篇。前兩天我們用 useState 管理元件內部的狀態、用 useEffect 同步到外部系統、用自訂 Hook 抽出可重用的邏輯。但當狀態需要在「多層元件」之間共享——例如「目前登入的使用者」、「主題(亮/暗)」、「語系」——光靠 props 一層層傳下去就會出現「props drilling」這種醜陋的程式碼。React 內建的 Context API 就是為了解決這個問題而設計。今天我們會把昨天的 usePersistentState 與 useFetch 組合成「全域使用者狀態 + 持久化」的管理系統,並用一個完整的「預約管理系統」的後台案例,把提升狀態、Context、useReducer 三種模式全部演練一次。整篇範例都在本機 CPU 跑得起來,不依賴雲端服務。

引言

寫後端的時候我們對「跨模組共享狀態」很熟悉:全域變數、單例物件、依賴注入容器。前端這邊因為 React 的「單向資料流」,狀態預設只能在元件樹中往下傳遞。當元件樹只有兩三層時,這種傳遞很自然;當元件樹長到五層、十層,把同一個 props 從最外層一直挖到最深處的子元件,就會出現「props drilling」——中間那幾層元件根本不關心這個 props,卻被迫當傳話的鏈條。

React 內建三個機制處理這個問題。第一個是「提升狀態」(lift state up):把狀態搬到共用它的最近共同祖先,讓祖先透過 props 分別傳給需要的子元件。這個手法最單純,但對深層元件樹仍然囉嗦。第二個是 Context API:在元件樹上層「宣告一個 Context」,下層任何元件都可以「訂閱」這個 Context 拿到值,不需要中間層傳遞。第三個是 useReducer:把多個相關的 state 與更新邏輯打包成 reducer,讓 Context 的內容更結構化。今天三個都會講,並且會示範它們在「預約管理系統」這個貫穿專案中的角色。

今天要回答五個問題:第一,什麼時候該用提升狀態,什麼時候該用 Context?第二,Context 的 API 怎麼寫?第三,Context 的效能陷阱是什麼?第四,什麼時候該升級到 useReducer 或外部狀態函式庫?第五,怎麼把昨天做的 Hook 跟 Context 接起來?我們會延續昨天的專案,把 useFetch 與 usePersistentState 組合起來做一個「全域登入狀態 + 主題切換 + 跨元件通知」的範例。整篇閱讀時間約 35 分鐘,動手做大約 40 分鐘。

提升狀態:最簡單的跨元件共享

在引入 Context 之前,先看一下「提升狀態」這個最簡單的手法。它的概念是把狀態從子元件搬到「共用它的最近共同祖先」,讓祖先負責持有 state,並透過 props 分別傳給需要它的子元件。後端工程師可以把這個手法想成「把區域變數提升到 service 層」。

// src/components/FilterableList.tsx
// 提升狀態範例:篩選條件與清單狀態都由父元件持有
import { useState } from "react";

type Item = { id: number; name: string; tag: string };

const allItems: Item[] = [
  { id: 1, name: "週三瑜伽課", tag: "瑜珈" },
  { id: 2, name: "週四重訓班", tag: "重訓" },
  { id: 3, name: "週五皮拉提斯", tag: "皮拉提斯" },
];

export function FilterableList() {
  // 篩選條件住在父元件:兩個子元件都讀得到
  const [keyword, setKeyword] = useState("");
  const filtered = allItems.filter((it) => it.name.includes(keyword));

  return (
    <div className="space-y-3">
      <SearchInput value={keyword} onChange={setKeyword} />
      <ItemList items={filtered} />
    </div>
  );
}

function SearchInput({ value, onChange }: { value: string; onChange: (v: string) => void }) {
  return (
    <input
      type="search"
      value={value}
      onChange={(e) => onChange(e.target.value)}
      placeholder="搜尋課程名稱"
      className="w-full rounded border border-slate-300 p-2 text-sm"
    />
  );
}

function ItemList({ items }: { items: Item[] }) {
  return (
    <ul className="space-y-1">
      {items.map((it) => (
        <li key={it.id} className="rounded border border-slate-200 p-2 text-sm">
          {it.name} <span className="text-slate-400">#{it.tag}</span>
        </li>
      ))}
    </ul>
  );
}

這個範例展示「提升狀態」的核心:keyword 與 filtered 都不住在 SearchInput 或 ItemList 裡,而是住在父元件 FilterableList。兩個子元件只用 props 接收自己需要的部分。當元件樹只有兩三層時,這個寫法最直覺,效能也最好——React 只在父元件 re-render 時才會重新渲染子元件,但子元件可以用 React.memo 進一步優化(Day 25 會講)。

提升狀態的限制是「元件樹深度」。如果共用同一個狀態的元件在元件樹中相隔五層以上,props 就要被中間五層元件「傳話」下去,這種「傳話」沒有意義、也容易出錯(中間某層忘記傳、某層改壞了),這就是「props drilling」。當這個問題出現時,就該考慮 Context。

Context API:跨多層元件共享狀態

React 的 Context API 讓你可以在元件樹的某個上層「宣告一個全域變數」,下層任何元件都可以「訂閱」這個變數,而不需要中間層傳遞 props。後端工程師可以把 Context 想成「全域變數的 React 版」——但比真正的全域變數安全,因為它只能在 Provider 子樹範圍內讀寫。

Context 由三個角色組成:

  1. 建立 Context:用 createContext 建立一個 Context 物件,附帶預設值。
  2. 提供 Context:在元件樹上層用 MyContext.Provider value={"..."} 把值注入。
  3. 消費 Context:下層元件用 useContext(MyContext) 取得值。

下面是 Context 的標準寫法,以「主題切換」為例:

// src/contexts/ThemeContext.tsx
// 建立 Theme Context:儲存目前主題(light / dark)與切換函式
import { createContext, useContext } from "react";

export type Theme = "light" | "dark";

export type ThemeContextValue = {
  theme: Theme;
  toggle: () => void;
};

// 建立 Context,並提供預設值(用在沒有 Provider 的場景)
export const ThemeContext = createContext<ThemeContextValue>({
  theme: "light",
  toggle: () => {},
});

// 自訂 Hook:封裝 useContext 呼叫,避免散落在各處
export function useTheme() {
  return useContext(ThemeContext);
}

這段程式建立一個 Context,並提供一個 useTheme 自訂 Hook 來消費它。Context 本體是「合約」,實際的值由 Provider 注入。預設值在「元件被用在 Provider 之外」時生效,正式開發通常不會用到,但它讓 TypeScript 型別檢查能通過。

接著寫 Provider。Provider 把 Context 的值注入到子樹:

// src/contexts/ThemeProvider.tsx
// Theme Provider:把主題值與切換函式注入子樹
import { useState, type ReactNode } from "react";
import { ThemeContext, type Theme } from "./ThemeContext";

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>("light");
  const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));

  return (
    <ThemeContext.Provider value={{ theme, toggle }}>
      {children}
    </ThemeContext.Provider>
  );
}

Provider 是一個普通的 React 元件,內部用 useState 管理狀態,並把狀態連同更新函式打包成物件透過 value 注入。任何被 {children} 渲染出來的子元件,都可以用 useTheme() 拿到 theme 與 toggle。

下層元件就能直接讀取主題,不需要 props 傳遞:

// src/components/ThemeToggleButton.tsx
// 主題切換按鈕:直接從 Context 讀取主題
import { useTheme } from "../contexts/ThemeContext";

export function ThemeToggleButton() {
  const { theme, toggle } = useTheme();
  return (
    <button
      type="button"
      onClick={toggle}
      className="rounded bg-slate-200 px-3 py-1 text-sm hover:bg-slate-300"
    >
      目前主題:{theme === "light" ? "亮" : "暗"}(點擊切換)
    </button>
  );
}

把 Provider 掛到 App 最外層,所有子元件就都能用 useTheme:

// src/App.tsx
// 把 ThemeProvider 放在最外層,讓所有子元件都能讀到主題
import { ThemeProvider } from "./contexts/ThemeProvider";
import { ThemeToggleButton } from "./components/ThemeToggleButton";

export default function App() {
  return (
    <ThemeProvider>
      <main className="min-h-screen bg-white p-8 text-slate-900">
        <h1 className="text-2xl font-bold">預約管理後台</h1>
        {/* 無論這個按鈕在元件樹多深,都能讀到主題 */}
        <div className="mt-4">
          <ThemeToggleButton />
        </div>
      </main>
    </ThemeProvider>
  );
}

這個範例展示 Context 的核心價值:ThemeToggleButton 在元件樹的任何位置(即使隔了五層 Layout)都能讀到主題,不需要中間層傳 props。這對「跨多層共享狀態」非常方便。

Context 的兩個效能陷阱

Context 並不是萬靈丹。它有兩個常見的效能陷阱,新手一定要知道。

第一個陷阱:「Context 的值每次都建立新的物件,會讓所有訂閱的元件全部 re-render」。這是 Context 最常見的踩雷:

// 反例:每次 render 都建立新的 value 物件,導致所有訂閱者 re-render
function BadProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>("light");
  return (
    <ThemeContext.Provider value={{ theme, toggle: () => setTheme((t) => (t === "light" ? "dark" : "light")) }}>
      {children}
    </ThemeContext.Provider>
  );
}

這段 Provider 每次 render 都會建立新的 value 物件。React 用「淺比較」判斷 Context 的值是否變化,新物件每次都「變」,所以所有訂閱 useTheme() 的元件都會重新渲染,即使 theme 值本身沒變。修法是把 value 用 useMemo 包起來,並把 toggle 用 useCallback 包起來:

// 正解:用 useMemo 把 value 包起來,避免不必要的 re-render
import { useCallback, useMemo, useState, type ReactNode } from "react";
import { ThemeContext, type Theme } from "./ThemeContext";

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>("light");

  const toggle = useCallback(() => {
    setTheme((t) => (t === "light" ? "dark" : "light"));
  }, []);

  const value = useMemo(() => ({ theme, toggle }), [theme, toggle]);

  return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}

第二個陷阱:「把 Context 拆太粗」。如果一個 Context 把「主題」、「使用者」、「語系」全部打包在一起,任何一個欄位變動都會讓所有訂閱者 re-render。修法是拆成多個小 Context,每個 Context 只管一件事:「ThemeContext」、「UserContext」、「LocaleContext」。這個原則跟後端的「單一職責」相同。

useReducer:當 state 邏輯變複雜

當 Context 裡的狀態只有一個布林值或一個字串時,useState 就夠了。但當狀態變成「多個欄位 + 多種更新動作」,例如「目前登入的使用者」、「載入狀態」、「錯誤訊息」、「token 過期時間」——直接用 useState 會讓 Provider 變得冗長。這時可以改用 useReducer:把 state 與更新邏輯打包成 reducer 函式。

// src/contexts/AuthContext.tsx
// 用 useReducer 管理登入狀態:多欄位 + 多動作
import { createContext, useContext, useReducer, type ReactNode } from "react";

export type AuthUser = { id: string; name: string; role: "user" | "admin" };
export type AuthState = {
  user: AuthUser | null;
  loading: boolean;
  error: string | null;
};

// 動作:把所有可能的更新打包成 discriminated union
export type AuthAction =
  | { type: "login/start" }
  | { type: "login/success"; user: AuthUser }
  | { type: "login/error"; error: string }
  | { type: "logout" };

function reducer(state: AuthState, action: AuthAction): AuthState {
  switch (action.type) {
    case "login/start":
      return { ...state, loading: true, error: null };
    case "login/success":
      return { user: action.user, loading: false, error: null };
    case "login/error":
      return { user: null, loading: false, error: action.error };
    case "logout":
      return { user: null, loading: false, error: null };
  }
}

const initial: AuthState = { user: null, loading: false, error: null };

type AuthContextValue = {
  state: AuthState;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
};

const AuthContext = createContext<AuthContextValue>({
  state: initial,
  login: async () => {},
  logout: () => {},
});

export function useAuth() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(reducer, initial);

  const login = async (email: string, password: string) => {
    dispatch({ type: "login/start" });
    try {
      const res = await fetch("/api/auth/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, password }),
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const user = (await res.json()) as AuthUser;
      dispatch({ type: "login/success", user });
    } catch (err) {
      dispatch({ type: "login/error", error: err instanceof Error ? err.message : "Unknown" });
    }
  };

  const logout = () => dispatch({ type: "logout" });

  return (
    <AuthContext.Provider value={{ state, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

這個 Provider 用 useReducer 取代 useState,原因是「登入」是一個有三個階段(start、success、error)的流程,加上 logout 共有四個動作。如果用 useState 寫,Provider 內部要呼叫 setState 三到四次;改用 useReducer 之後,邏輯集中在 reducer 函式,Provider 只需要呼叫 dispatch。這個模式跟 Redux 是同一個家族——只是 useReducer 內建在 React,不用裝額外套件。

下游元件就可以直接讀取登入狀態:

// src/components/Header.tsx
// 頁首:根據登入狀態顯示「登入」或「使用者名稱 + 登出」
import { useAuth } from "../contexts/AuthContext";

export function Header() {
  const { state, logout } = useAuth();
  return (
    <header className="flex items-center justify-between border-b border-slate-200 p-4">
      <h1 className="text-lg font-semibold">預約管理</h1>
      {state.user ? (
        <div className="flex items-center gap-3 text-sm">
          <span>Hello, {state.user.name}</span>
          <button
            type="button"
            onClick={logout}
            className="rounded bg-slate-200 px-3 py-1 hover:bg-slate-300"
          >
            登出
          </button>
        </div>
      ) : (
        <span className="text-sm text-slate-500">未登入</span>
      )}
    </header>
  );
}

把昨天的 Hook 與今天的 Context 接起來

昨天的 usePersistentState 可以讓 Context 的值自動同步到 localStorage,做成「重整後仍保留登入狀態」的效果:

// src/contexts/AuthProvider.tsx(升級版)
// 把昨天的 usePersistentState 整合進 AuthProvider
import { useReducer, useMemo, type ReactNode } from "react";
import { AuthContext, type AuthState, initialState, reducer } from "./authReducer";
import { usePersistentState } from "../hooks/usePersistentState";

export function AuthProvider({ children }: { children: ReactNode }) {
  // 把整個 state 序列化到 localStorage;重整後自動讀回
  const [persisted, setPersisted] = usePersistentState<AuthState>(
    "auth:state",
    initialState,
  );
  const [state, dispatch] = useReducer(reducer, persisted);

  // 當 reducer 更新 state 時,把結果同步回 localStorage
  // 用 useEffect 包起來(昨天教的模式)
  useEffect(() => {
    setPersisted(state);
  }, [state, setPersisted]);

  // ...login / logout 同上
}

這個升級版讓登入狀態在重整後仍保留:使用者不必每次重新整理都重新登入。實務上要注意「token 過期」的處理——通常會在 Day 22「認證與 session 管理」再展開,這裡先展示「Hook + Context 怎麼組合」。

什麼時候該升級到外部狀態函式庫

Context + useReducer 已經能處理大多數前端專案的狀態管理。但當以下情況出現時,就該考慮外部函式庫(Zustand、Jotai、Redux Toolkit):

  1. Context 太多:應用有 5 個以上 Context,每個有自己的 Provider,包裝鏈太長。
  2. 跨頁籤同步:不同分頁的狀態需要同步,Context 無法直接處理(要靠 storage 事件,昨天 Day 10 教的 usePersistentState 擴充版可以幫上忙)。
  3. 狀態很大且需要選擇器:某些元件只需要其中一個欄位,但 Context 變動會讓所有訂閱者 re-render。Zustand 等函式庫提供 selector 機制,能讓元件只訂閱自己需要的部分。
  4. DevTools 需求:需要時間旅行除錯、狀態快照匯出。Redux DevTools 提供完整的體驗。

本系列在 Day 31–44 的「預約管理系統」會繼續用 Context + useReducer 主軸,只在「跨元件通知」與「全域快取」需要時才升級。原因是 Context 對 React 內建、零依賴、新人易上手,符合「能內建就不裝外部」的原則。

常見錯誤與踩雷

第一個踩雷是「Provider 沒放在最外層」。Context 只能向下傳遞,如果 Provider 放在某個深層元件,它下層的元件讀不到,上層的元件也讀不到。常見的錯誤是把 ThemeProvider 放在 App 元件裡面、卻在某個路由的 layout 元件外面用 useTheme,結果讀到預設值。修法是把 Provider 放在最靠近根節點的位置。

第二個踩雷是「忘記把 Provider 包到所有需要的地方」。例如你寫了 UserContext,但只有登入頁有用 Provider,導致後台頁面讀不到 user 資料。修法是把所有 Provider 集中在 src/providers/AppProviders.tsx,並在 main.tsx 一次包好。

第三個踩雷是「Provider 的 value 直接放物件字面值」。第一個陷阱提過,每次 render 都建立新的物件會讓所有訂閱者 re-render。即使這個 re-render 不影響正確性,也會浪費效能。修法是用 useMemo。

第四個踩雷是「reducer 內呼叫 setState 或 fetch」。Reducer 必須是純函式:給定相同的 state 與 action,必須回傳相同的結果。如果 reducer 內呼叫 fetch 或 setState,會讓狀態變得不可預期,也無法被 DevTools 時間旅行。修法是把副作用放在 Provider 的事件處理函式(例如 login),只把結果用 dispatch 送進 reducer。

第五個踩雷是「在 SSR 環境忘記處理」。Context 的 Provider 在 Next.js 的 Server Component 裡建立時,Client Component 訂閱到的會是「序列化後的值」,無法即時更新。修法是把 Provider 標記為 Client Component,或在 Server Component 裡用 getServerSession 取得資料再傳給 Provider。

效能與實務提醒

Context 的效能影響主要在「Provider 的 value 變化時,所有訂閱者 re-render」。可以用三個手段降低影響。第一,把 Context 拆細:主題一個、使用者一個、語系一個,避免單一 Context 變動影響太多元件。第二,用 useMemo 把 value 包起來,避免物件參考不穩定。第三,用 React.memo 把「不需要訂閱 Context 的子元件」包起來,這樣 Provider re-render 不會拖累它們。

另一個實務提醒是「Context 適合低頻變動的狀態」。例如「目前使用者」、「主題」、「語系」這些「應用程式生命週期內只變幾次」的狀態,Context 沒問題。但「捲動位置」、「滑鼠座標」、「動畫幀數」這種「每秒變幾十次」的狀態,就不該放 Context——這些資料變動太頻繁,會讓整個應用陷入 re-render 風暴。這類狀態適合用 ref 或外部狀態函式庫。

最後提一個測試注意事項:「Provider 在測試裡要手動包」。當元件內部呼叫 useAuth(),但測試渲染時沒有 Provider,會拿到預設值(空函式、空狀態)。修法是在測試裡用 render() 包起來,或建立一個測試專用的 wrapper。Day 15「測試」會再展開這個主題。

另外要留意 Provider 的生命週期。Context 的值是在 Provider 元件內部用 useState 或 useReducer 持有的,當 Provider 從畫面移除時(典型情境是路由切換到另一個 layout),Context 的值也會跟著消失。如果某個深層子元件在 Provider 離開後仍嘗試讀取 Context,會拿到預設值(因為它已經不在 Provider 子樹內)。這是 React 的設計——Provider 是狀態的「邊界」,離開這個邊界就看不到值。實務上 App 的根 Provider 通常不會被移除,所以這個問題不太常見;但在設計多個獨立 Provider(例如登入前 vs 登入後的 Layout)時要特別注意。

另一個團隊協作的實務:「Provider 集中在 src/providers/AppProviders.tsx」。把所有 Provider 集中在一個檔案,未來要看「這個 App 套了哪些 Provider」只要看這個檔案。新成員加入時,也容易從這裡理解 Context 的整體結構。下面是一個典型的 AppProviders:

// src/providers/AppProviders.tsx
// 把所有 Provider 集中起來,方便管理與測試
import type { ReactNode } from "react";
import { ThemeProvider } from "../contexts/ThemeProvider";
import { AuthProvider } from "../contexts/AuthProvider";

export function AppProviders({ children }: { children: ReactNode }) {
  return (
    <ThemeProvider>
      <AuthProvider>
        {children}
      </AuthProvider>
    </ThemeProvider>
  );
}

用 AppProviders 包好之後,main.tsx 只要呼叫一次 AppProviders 就把所有 Context 套上去,不必在每個入口檔案記得 import 每個 Provider。

小結

今天我們把跨元件狀態管理從「提升狀態」到「Context API」再到「useReducer」一次走完。重點回顧:提升狀態是「把狀態搬到共同祖先」,簡單直覺但對深層元件樹不友善;Context API 用 Provider/Consumer 模型讓任何深度的子元件都能讀到狀態,但要注意 value 物件的參考穩定性與 Context 拆分;useReducer 把多欄位、多動作的狀態邏輯集中到 reducer 函式,適合登入流程、表單狀態這類較複雜的場景;當 Context 太多或需要 selector 時,才考慮升級到外部狀態函式庫。我們也用 AuthProvider + usePersistentState 展示了「全域狀態 + 持久化」的組合,這個模式會在 Day 22「認證」與 Day 31 之後的「預約管理系統」繼續使用。明天 Day 12 會進入表單主題——受控元件、非受控元件、Zod 驗證、錯誤狀態的處理,這是 React 開發每天都在做的事。

結語

明天,我們會把狀態管理的焦點從「跨元件共享」轉到「表單處理」。Day 12 會介紹 React 表單的兩種模式——受控元件(value + onChange)與非受控元件(ref + defaultValue)——以及怎麼用 Zod 做欄位驗證、怎麼處理錯誤狀態、怎麼避免「每次打字都 re-render」的效能問題。我們會做一個完整的「預約建立表單」,把你熟悉的後端表單經驗搬到 React 上。記得今天的 AuthProvider 與 useAuth Hook 先留著,明天會把「表單送出時的登入狀態檢查」整合進去。

延伸資源

  • React 官方〈Passing Data Deeply with Context〉(19.x,2026 年 3 月):https://react.dev/learn/passing-data-deeply-with-context
  • React 官方〈Scaling Up with Reducer and Context〉(19.x):https://react.dev/learn/scaling-up-with-reducer-and-context
  • React 官方〈useReducer 參考〉(19.x):https://react.dev/reference/react/useReducer
  • Zustand 官方文件(Day 31 之後可能用到的輕量狀態函式庫):https://zustand-demo.pmnd.rs/
  • TypeScript Handbook〈Discriminated Unions〉(reducer 動作設計):https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions

留言

這個網誌中的熱門文章

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 建構深度學習模型。 開發者與研究人員 :想更深入了...