跳到主要內容

FE Day 10 自訂 Hook:把邏輯變成可重用的積木

FE Day 10 自訂 Hook:把邏輯變成可重用的積木

執行需求:CPU 可跑。今天是系列的第十篇,正式進入「核心」章節。昨天的 useEffect 範例裡,我們把「自動儲存到 localStorage」的邏輯寫成 usePersistentDraft 函式;那支函式呼叫了 useState 與 useEffect,本身已經是 Hook 的雛形。今天要把這件事攤開來講:自訂 Hook(custom hook)是 React 重用「帶狀態邏輯」的標準機制,比元件拆解更高階、比工具函式更有彈性。我們會把昨天的範例升級成可重用版本,再用三個小型 Hook(useToggle、useDebounce、useFetch)演示「組合 Hook」的威力。整篇範例都在本機 CPU 跑得起來,不依賴雲端服務。

引言

寫後端的時候我們對「邏輯重用」很熟悉:把常用的操作抽成 service 函式、把工具方法放進 utils 模組、把一組相關的常數放進 constants 檔。前端這邊有一個對應機制,但層級不同:React 18 之後興起的「Hook」不只是「重用程式碼」,更是「重用『帶狀態』的程式碼」。一段邏輯如果有用到 useState 或 useEffect,就適合做成 Hook;如果只是純函式,放進 utils 模組即可。

自訂 Hook 不是 React 的新 API,而是「用 useState 等內建 Hook 組合出來的函式」。這個函式遵守兩個規則才能被 React 識別為 Hook:第一,名稱必須以 use 開頭(useToggle、useFetch、useDebounce);第二,在它裡面呼叫其他 Hook 時,每次 render 都要用同樣的順序、同樣的數量。這兩個規則讓 React 的 Hook 檢查機制(linter)能正確運作。

今天要解決五個問題:第一,自訂 Hook 的本質是什麼?第二,怎麼從現有的元件程式碼抽出 Hook?第三,Hook 之間怎麼組合?第四,Hook 怎麼回傳資料才容易用?第五,常見的命名與依賴踩雷。我們會延續昨天的 usePersistentDraft,把它升級成「支援驗證、多欄位、跨分頁同步」的版本,並用 useDebounce、useToggle、useFetch 演示「組合 Hook」的威力。讀完之後你應該能判斷「什麼邏輯該做成 Hook、什麼邏輯該留在元件裡」。整篇閱讀時間約 30 分鐘,動手做大約 35 分鐘。

自訂 Hook 的本質

自訂 Hook 就是一個「呼叫了其他 Hook 的函式」。它不需要任何特殊的 return 型別、不需要繼承某個類別、不需要註冊到 React 內部。只要遵守命名規則,React 函式元件就可以像呼叫普通函式一樣呼叫它。下面是最小的範例:

// src/hooks/useToggle.ts
// 最小的自訂 Hook:把布林值的切換邏輯抽出來
import { useState } from "react";

export function useToggle(initial: boolean = false) {
  const [value, setValue] = useState(initial);
  // 切換函式:呼叫時把布林值反過來
  const toggle = () => setValue((v) => !v);
  // 回傳 [目前值, 切換函式, 強制設值函式],用法類似 useState
  return [value, toggle, setValue] as const;
}

這個 Hook 看起來跟寫在元件裡的程式碼沒兩樣,差別在於它「被抽出來、可以重複使用」。元件裡如果呼叫 useToggle(),React 內部會把它視為「這個元件呼叫了 useState」——換句話說,自訂 Hook 只是「把多個 Hook 的呼叫打包成一個函式」。這也意味著:Hook 內部的 state 是「每次呼叫各自獨立」的。同一個元件裡呼叫兩次 useToggle,會得到兩個獨立的布林值。

為什麼要命名為 use 開頭?因為 React 的 ESLint 規則(react-hooks/rules-of-hooks)只掃描所有 use 開頭的函式,並檢查「Hook 不能在條件式、迴圈、巢狀函式裡呼叫」。如果你的函式沒以 use 開頭,linter 不會檢查它;呼叫順序錯了就會出 bug。所以命名不是裝飾品,是 linter 的觸發條件,也是 React 社群辨識「這是 Hook」的訊號。

Hook 不是元件

新手常誤把 Hook 當元件寫,導致「Hook 回傳 JSX」這種奇怪的程式碼。Hook 回傳的應該是「資料與函式」,由元件決定怎麼把這些資料變成畫面。如果你的 Hook 包含 JSX,那它就是元件,應該用 .tsx 寫成 React component 而不是 Hook:

// 反例:Hook 不該回傳 JSX(會把資料邏輯跟畫面綁死)
function useUserCard() {
  const user = useUser();
  return <div>{user.name}</div>; // 這不是 Hook,這應該是 UserCard 元件
}

// 正例:Hook 回傳資料,元件負責渲染
function useUser() {
  const [user, setUser] = useState<User | null>(null);
  return { user, setUser };
}

function UserCard() {
  const { user } = useUser();
  return <div>{user?.name ?? "載入中"}</div>;
}

分辨的方法很簡單:寫完之後問自己「這段邏輯要不要跟畫面綁在一起?」要,就寫成元件;不要,就寫成 Hook。Hook 的目的是「把狀態邏輯抽離畫面」,元件的目的是「把畫面跟資料綁定」。一個常見的判斷口訣是:Hook 裡不該出現 return 帶 JSX 的寫法(也就是類似 return

...
),只要出現就是元件。

從元件抽出 Hook 的標準流程

把昨天的 usePersistentDraft 重新看一下,思考「如果第二個元件也想用同樣的自動儲存功能,要怎麼重用?」實務上的標準流程是三步:

  1. 找出「純邏輯」的部分(state 宣告、effect 設定、cleanup 邏輯)。
  2. 把這些邏輯搬到一個以 use 開頭的函式。
  3. 把函式需要的「輸入」當參數傳入、回傳「輸出」(state 與 setter)。

昨天我們已經寫了 usePersistentDraft 的雛形。今天把它升級成「支援驗證、跨分頁同步、自訂序列化」的版本。第一個範例是升級版的 usePersistentState,搭配 Zod 做 schema 驗證:

// src/hooks/usePersistentState.ts
// 自訂 Hook:把任意可序列化 state 同步到 localStorage,並用 Zod 驗證
import { useEffect, useState } from "react";
import { z } from "zod";

type Options<T> = {
  // 驗證 schema:若提供,從 localStorage 讀回的資料會被驗證
  schema?: z.ZodType<T>;
  // 序列化函式(預設 JSON.stringify)
  serialize?: (value: T) => string;
  // 反序列化函式(預設 JSON.parse)
  deserialize?: (raw: string) => unknown;
};

const defaultSerialize = (v: unknown) => JSON.stringify(v);
const defaultDeserialize = (raw: string) => JSON.parse(raw);

export function usePersistentState<T>(
  key: string,
  initial: T,
  options: Options<T> = {},
) {
  const {
    schema,
    serialize = defaultSerialize,
    deserialize = defaultDeserialize,
  } = options;

  // 延遲初始化:第一次 render 時才讀 localStorage(避免 SSR 出錯)
  const [value, setValue] = useState<T>(() => {
    if (typeof window === "undefined") return initial;
    const raw = window.localStorage.getItem(key);
    if (raw === null) return initial;
    try {
      const parsed = deserialize(raw);
      // 用 schema 驗證讀回的資料;驗證失敗就退回 initial
      if (schema) {
        const result = schema.safeParse(parsed);
        return result.success ? result.data : initial;
      }
      return parsed as T;
    } catch {
      return initial;
    }
  });

  // 每次 value 改變時同步寫回
  useEffect(() => {
    if (typeof window === "undefined") return;
    try {
      window.localStorage.setItem(key, serialize(value));
    } catch (err) {
      console.warn("usePersistentState 寫入失敗", err);
    }
  }, [key, value, serialize]);

  return [value, setValue] as const;
}

這個 Hook 把昨天的範例擴充成「泛型搭配驗證與序列化自訂」。在元件裡使用時,只要傳入 key、初始值、schema,就能拿到一組「會自動同步到 localStorage」的 state。特別注意兩個設計細節:第一,泛型 T 讓 Hook 可以承載任何形狀的資料,第二參數 initial 的型別會自動推導;第二,serialize 與 deserialize 函式用 options 物件包起來,未來要支援 base64、壓縮、自訂日期格式都不用改 Hook 本體。

在元件裡使用就非常乾淨:

// src/components/NoteEditor.tsx(升級版)
// 多欄位自動儲存:用 usePersistentState 儲存整份草稿物件
import { z } from "zod";
import { usePersistentState } from "../hooks/usePersistentState";

const DraftSchema = z.object({
  title: z.string().min(1, "標題不可為空").max(100),
  body: z.string().max(5000),
});

type Draft = z.infer<typeof DraftSchema>;

const emptyDraft: Draft = { title: "", body: "" };

export function NoteEditor() {
  const [draft, setDraft] = usePersistentState<Draft>("note:draft", emptyDraft, {
    schema: DraftSchema,
  });

  return (
    <form className="mx-auto max-w-xl space-y-3 p-4">
      <label className="block">
        <span className="text-sm font-medium text-slate-700">標題</span>
        <input
          type="text"
          value={draft.title}
          onChange={(e) => setDraft({ ...draft, title: e.target.value })}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        />
      </label>

      <label className="block">
        <span className="text-sm font-medium text-slate-700">內容</span>
        <textarea
          value={draft.body}
          onChange={(e) => setDraft({ ...draft, body: e.target.value })}
          rows={6}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        />
      </label>

      <p className="text-xs text-slate-500">
        目前標題 {draft.title.length} 字、內容 {draft.body.length} 字(重整後仍會保留)
      </p>
    </form>
  );
}

這段範例展示「自訂 Hook 重用邏輯」的核心價值:NoteEditor 不再碰任何 useEffect、localStorage、try/catch,全部交給 usePersistentState 處理。如果明天要再加一個「自動儲存使用者偏好」的元件,只要呼叫 usePersistentState 傳不同的 key 即可,零程式碼複製。

跨分頁同步:把 storage 事件串進來

localStorage 有一個特性:在同一個瀏覽器的不同分頁之間,「storage」事件會自動觸發。這意味著如果某個分頁改了 localStorage 的某個 key,其他分頁可以監聽到這個事件。我們可以在 Hook 裡訂閱這個事件,讓多個分頁的 state 保持同步:

// src/hooks/usePersistentState.ts(擴充版)
// 在上一版的基礎上,加上跨分頁同步
import { useEffect, useState } from "react";
import { z } from "zod";

export function usePersistentState<T>(
  key: string,
  initial: T,
  options: Options<T> = {},
) {
  const { schema, serialize = defaultSerialize, deserialize = defaultDeserialize } = options;
  const [value, setValue] = useState<T>(() => readInitial(key, initial, schema, deserialize));

  // 寫入:value 改變時同步寫回 localStorage
  useEffect(() => {
    if (typeof window === "undefined") return;
    try {
      window.localStorage.setItem(key, serialize(value));
    } catch (err) {
      console.warn("usePersistentState 寫入失敗", err);
    }
  }, [key, value, serialize]);

  // 跨分頁同步:當其他分頁改了同一個 key,把 value 也更新
  useEffect(() => {
    if (typeof window === "undefined") return;
    const onStorage = (e: StorageEvent) => {
      if (e.key !== key || e.newValue === null) return;
      try {
        const parsed = deserialize(e.newValue);
        const next = schema ? (schema.safeParse(parsed).data ?? initial) : (parsed as T);
        setValue(next);
      } catch {
        // 忽略解析失敗
      }
    };
    window.addEventListener("storage", onStorage);
    return () => window.removeEventListener("storage", onStorage);
  }, [key, schema, deserialize, initial]);

  return [value, setValue] as const;
}

這個擴充版多了「跨分頁同步」的能力:在 A 分頁編輯草稿、B 分頁會自動更新成同一份內容。實務上很適合「同個使用者在多個分頁開著同一個後台」的情境。注意 storage 事件只在「其他分頁」觸發,自己寫入的分頁不會收到——所以不需要擔心「自己寫入又自己 setState 造成迴圈」。

useDebounce:用一個 Hook 解決「打字時不要每個字都打 API」

另一個常見的自訂 Hook 是 useDebounce。它的用途是「把快速變化的值『延遲』成一個穩定的值」:例如使用者打字時,搜尋框的值每秒可能變 5 次,但 API 不需要被打 5 次——只要在使用者停下來 300 毫秒之後才打一次就好。useDebounce 把這個邏輯封裝起來:

// src/hooks/useDebounce.ts
// 自訂 Hook:延遲一段時間才更新值
import { useEffect, useState } from "react";

export function useDebounce<T>(value: T, delayMs: number = 300): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    // 設定一個計時器:delayMs 後才把 debounced 更新成 value
    const id = setTimeout(() => setDebounced(value), delayMs);

    // cleanup:下次 effect 跑或元件被移除時,清掉這個計時器
    return () => clearTimeout(id);
  }, [value, delayMs]);

  return debounced;
}

這個 Hook 用到「昨天的 useEffect 規則」:每次 value 變化時啟動計時器,並在 cleanup 中清除。這樣當 value 連續變化時,只有最後一次的計時器會真正執行,中間的都被 cleanup 清掉。使用方式很簡單——把「原始值」傳進去,拿到「延遲後的值」:

// src/components/SearchBox.tsx
// 搜尋框:使用者停止打字 300ms 後才打 API
import { useEffect, useState } from "react";
import { useDebounce } from "../hooks/useDebounce";

export function SearchBox({ onSearch }: { onSearch: (q: string) => void }) {
  const [keyword, setKeyword] = useState("");
  const debounced = useDebounce(keyword, 300); // 300ms 後才更新

  // 當 debounced 變了才打 API(避免每打一個字就送一次)
  useEffect(() => {
    if (!debounced) return;
    onSearch(debounced);
  }, [debounced, onSearch]);

  return (
    <input
      type="search"
      value={keyword}
      onChange={(e) => setKeyword(e.target.value)}
      placeholder="輸入關鍵字"
      className="w-full rounded border border-slate-300 p-2 text-sm"
    />
  );
}

這個範例展示「Hook 內部使用另一個 Hook」——useDebounce 內部呼叫了 useState 與 useEffect,元件裡又呼叫 useDebounce 與 useEffect。每次元件 render 時,Hook 的呼叫順序都一樣(先是 useState、再是 useDebounce、再是 useEffect),符合 Hook 規則。

Hook 的回傳值設計紀律

自訂 Hook 的回傳方式沒有強制規定,但有幾個常見的模式可以選。選對模式能讓呼叫端更舒服,選錯會讓元件讀起來很彆扭。下面三種是 React 社群最常用的:

  1. 元組(tuple):回傳 [value, setter],像 useState 一樣。適合「主要回傳一個值」的 Hook,例如 useToggle、useDebounce。
  2. 物件(object):回傳 { value, setter, extra },適合「同時回傳多個相關欄位」的 Hook,例如 useFetch 回傳 { data, error, isLoading }。
  3. 只有副作用:不回傳值,只在某個值改變時做事。例如 useDocumentTitle(title),每次 title 變化就把 document.title 同步過去。

實務上的選擇原則:「只有一個主要輸出」用元組;「多個獨立欄位」用物件;「純粹副作用」不需要回傳值。元組的優點是「呼叫端可以自己命名」,例如 const [keyword, setKeyword] = useDebounce(...);物件的優點是「呼叫端可以一次拿到多個東西,但欄位名稱被 Hook 綁死」。

下面是 useFetch 範例,用物件回傳三個欄位:

// src/hooks/useFetch.ts
// 自訂 Hook:抓取 JSON 資料,回傳 data / error / loading
import { useEffect, useState } from "react";

type FetchState<T> = {
  data: T | null;
  error: Error | null;
  loading: boolean;
};

export function useFetch<T>(url: string): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    error: null,
    loading: true,
  });

  useEffect(() => {
    const ctrl = new AbortController();
    let cancelled = false;

    async function run() {
      setState({ data: null, error: null, loading: true });
      try {
        const res = await fetch(url, { signal: ctrl.signal });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const data = (await res.json()) as T;
        if (!cancelled) setState({ data, error: null, loading: false });
      } catch (err) {
        if (cancelled) return;
        setState({
          data: null,
          error: err instanceof Error ? err : new Error("Unknown"),
          loading: false,
        });
      }
    }
    run();

    return () => {
      cancelled = true;
      ctrl.abort();
    };
  }, [url]);

  return state;
}

使用方式:

// src/components/UserList.tsx
import { useFetch } from "../hooks/useFetch";

type User = { id: number; name: string };

export function UserList() {
  const { data, error, loading } = useFetch<User[]>("/api/users");

  if (loading) return <p className="text-slate-500">載入中…</p>;
  if (error) return <p className="text-red-600">錯誤:{error.message}</p>;
  if (!data) return null;

  return (
    <ul className="space-y-1">
      {data.map((u) => (
        <li key={u.id} className="rounded border border-slate-200 p-2 text-sm">
          {u.name}
        </li>
      ))}
    </ul>
  );
}

注意 useFetch 用了 cancelled 旗標(昨天 useEffect 章節提過的技巧):元件被移除時,正在進行的 fetch 即使回來,也不會 setState。Day 14 會介紹更強大的 TanStack Query,這裡的 useFetch 主要是為了展示「Hook 怎麼回傳物件」。

常見錯誤與踩雷

第一個踩雷是「Hook 名稱不以 use 開頭」。React 的 linter 只檢查以 use 開頭的函式,所以如果你命名為 fetchUser() 卻在裡面呼叫 useState,linter 不會幫你抓「在條件式裡呼叫」這種 bug。修法很簡單:只要函式裡有用到 Hook,名稱就要以 use 開頭。

第二個踩雷是「Hook 在條件式或迴圈裡被呼叫」。這是 Hook 的根本禁令——React 用「呼叫順序」來區分多次呼叫的結果,如果在條件式裡呼叫,順序會亂掉,所有 state 對應關係就錯了:

// 反例:條件式呼叫 Hook(會壞掉)
function Form({ enabled }: { enabled: boolean }) {
  if (enabled) {
    const [value, setValue] = useState(""); // 違反 Hook 規則
  }
  // 後續邏輯會拿到錯的 value
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

// 正解:把條件移到 Hook 內部
function Form({ enabled }: { enabled: boolean }) {
  const [value, setValue] = useState("");
  // 用 enabled 控制副作用或行為,不要控制 Hook 本身
  useEffect(() => {
    if (enabled) doSomething(value);
  }, [enabled, value]);
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

第三個踩雷是「Hook 回傳不穩定的參考」。如果你的 Hook 回傳一個「每次 render 都被重新產生的物件或函式」,會導致呼叫端的 useEffect 與 useMemo 一直以為依賴變了。修法是用 useMemo 或 useCallback 包起來,或在 Hook 內部就把物件組好:

// 反例:每次 render 都回傳新物件(呼叫端的 useEffect 會無限觸發)
function useUser() {
  const [user, setUser] = useState(null);
  return { user, setUser }; // 新物件,每次 render 都「變」
}

// 正解:把物件用 useMemo 包起來
function useUser() {
  const [user, setUser] = useState(null);
  return useMemo(() => ({ user, setUser }), [user]);
}

第四個踩雷是「Hook 之間互相耦合」。例如 useBookingFlow() 內部用了 useUser(),useUser() 內部又用了 usePermission()。這種層層依賴的 Hook 寫起來容易,除錯時很難定位是哪一層壞了。設計原則:Hook 之間的依賴不要超過兩層,否則就該考慮「合併成一個 Hook」或「把通用邏輯拆出來」。

第五個踩雷是「Hook 沒處理 SSR」。在 Next.js 裡,如果你的 Hook 直接呼叫 window.localStorage 而沒用 typeof window !== "undefined" 判斷,伺服器渲染時會丟出例外。今天的 usePersistentState 範例已經預先判斷過,搬到 Next.js 時不會壞。

效能與實務提醒

自訂 Hook 對效能的影響主要是「重新渲染」。React 不會因為「元件呼叫了自訂 Hook」就多 re-render 一次——Hook 內部的 useState 跟元件直接呼叫 useState 一樣,都是「這個元件多一個 state」。但如果你的 Hook 內部用了 useMemo 或 useCallback,會增加一點額外的依賴比對成本,通常可以忽略不計。

另一個實務提醒是「Hook 測試」。自訂 Hook 的測試方式比元件簡單:用 React Testing Library 的 renderHook 工具(Day 15 會詳細教)就能像測試元件一樣測 Hook。重點是「讓 Hook 的依賴(fetch、storage、計時器)可以 mock」。今天的 usePersistentState 在測試時可以 mock localStorage,useFetch 可以 mock fetch 函式,useDebounce 可以用 fake timers 控制時間。

最後一個提醒是「不要過度抽象」。並不是每一段 useState 都該抽成 Hook。如果某段邏輯只在一個元件裡用一次,抽成 Hook 只會增加檔案數、降低可讀性。等到「同一段邏輯在第二個元件出現」或「邏輯複雜到影響元件可讀性」時,再抽 Hook 即可。常見的判斷指標是「這個函式被兩個以上檔案 import」。本系列後續 Day 16「專案結構」會再討論這個取捨。

順帶提一個團隊協作的實務:「Hook 集中放在 src/hooks/」。這個資料夾就是「團隊的邏輯函式庫」。每個 Hook 一個檔案,名稱就是檔名(useToggle.ts、useDebounce.ts),使用端用 import { useToggle } from "@/hooks/useToggle" 引用。如果專案用 Next.js,可以用 TypeScript path alias(@/hooks/*)減少相對路徑。當新成員加入時,只要看 hooks 資料夾就能快速掌握「這個專案有哪些可重用的邏輯」。

小結

今天我們把自訂 Hook 從觀念到實作一次走完。重點回顧:自訂 Hook 是「呼叫其他 Hook 的函式」,命名以 use 開頭,回傳資料而非 JSX;從元件抽出 Hook 的標準流程是「找邏輯 → 搬到 use 函式 → 傳輸入回輸出」;常見的回傳模式有元組、物件、純副作用三種,根據「主要輸出數量」選擇;常見踩雷包括名稱不以 use 開頭、條件式呼叫、回傳不穩定參考、過度耦合。我們也實作了 usePersistentState、useDebounce、useToggle、useFetch 四個範例 Hook,示範了「Hook 內組合 Hook」的實務做法。明天 Day 11 會進入狀態管理的主題——Context API 怎麼用、什麼時候該用、什麼時候不該用,以及「提升狀態」這個經典模式怎麼用在我們的預約系統上。

結語

明天,我們會把狀態管理的範圍從「元件內部」擴展到「跨元件」。Day 11 會介紹 React 的 Context API,示範怎麼用它傳遞「目前使用者」、「主題」、「語系」這類跨多層元件的狀態,並討論它的限制與替代方案(props drilling、提升狀態、useReducer、外部狀態函式庫)。記得今天的 usePersistentState 與 useFetch 先留著,明天會在同一個專案上把它們組合成「全域使用者狀態 + 持久化」的版本。

延伸資源

  • React 官方〈Reusing Logic with Custom Hooks〉(19.x,2026 年 3 月):https://react.dev/learn/reusing-logic-with-custom-hooks
  • React 官方〈Hook 規則〉:https://react.dev/reference/rules/rules-of-hooks
  • Zod 官方說明(Day 9/10 範例使用):https://zod.dev/
  • useHooks 社群範例集:https://usehooks.com/
  • React Hooks ESLint 插件:https://www.npmjs.com/package/eslint-plugin-react-hooks

留言

這個網誌中的熱門文章

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