跳到主要內容

FE Day 12 表單與受控元件

FE Day 12 表單與受控元件

執行需求:CPU 可跑。今天是系列的第十二篇。前幾天我們學會了 useState、useEffect、自訂 Hook 與 Context,這四樣東西加起來已經能處理大多數 React 元件的狀態邏輯。今天要把它們全部用在一個真實的工作場景——「表單」。後端工程師對表單並不陌生:使用者填欄位、前端驗證、送出 HTTP 請求、後端驗證、回傳結果。但 React 表單有兩個特殊觀念:受控元件(controlled component)與非受控元件(uncontrolled component),掌握之後才能正確處理「使用者一邊打字一邊驗證」、「送出時一次驗證全部欄位」、「送出後保留輸入」這些情境。今天會做一個完整的「預約建立表單」,把 React Hook Form 與 Zod 整合起來,順便把昨天學的 Context 用在「使用者必須登入才能送出」這個流程上。整篇範例都在本機 CPU 跑得起來,不依賴雲端服務。

引言

寫後端時,表單處理是「收到 request、表單驗證、寫進資料庫、回 response」這條流程。React 表單的前半段邏輯類似:「使用者輸入欄位、瀏覽器即時更新值、按下送出時驗證、呼叫後端 API」。差別在於「即時更新值」這件事——HTML 表單天生會保留使用者輸入,但 React 的世界裡,我們有兩種方式讀取輸入值:用 React state「控制」欄位值(受控),或讓瀏覽器自己保留欄位值、用 ref「讀出來」(非受控)。

這兩種模式各有優缺。受控元件的優點是「React 永遠知道欄位的值」,可以做即時驗證、條件式 disable 按鈕、依其他欄位動態改變欄位內容。缺點是「每次使用者打字都會 re-render 元件」,對大型表單可能有效能影響。非受控元件的優點是「效能好、寫起來簡單」,缺點是「React 不知道欄位的值」,要讀值時才從 DOM 拉。實務上 90% 的表單用受控元件即可,剩下的 10%(例如檔案上傳、大型表單)才考慮非受控。

今天要回答五個問題:第一,受控元件與非受控元件的差別是什麼?第二,怎麼用 Zod 驗證單一欄位?第三,怎麼在「使用者打字時」與「送出時」分別觸發驗證?第四,React Hook Form 怎麼用、為什麼推薦?第五,怎麼處理「送出失敗但保留使用者輸入」這個情境?我們會做一個「建立預約」的完整表單:欄位包含姓名、Email、日期、時段、備註,按下送出時檢查登入狀態、呼叫 API、把結果顯示在畫面上。整篇閱讀時間約 35 分鐘,動手做大約 40 分鐘。

受控元件:React 永遠知道欄位值

受控元件的核心是兩個 props:value 與 onChange。React 把欄位的值存進 state,使用者每次輸入就觸發 onChange、更新 state,然後 React 把新的 state 傳回 value,欄位就顯示新值。後端工程師可以把它想成「React 永遠是 single source of truth」。

以下是受控輸入框的最基本寫法:

// src/components/NameField.tsx
// 最小的受控輸入框
import { useState } from "react";

export function NameField() {
  const [name, setName] = useState("");

  return (
    <label className="block">
      <span className="text-sm font-medium text-slate-700">姓名</span>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        placeholder="請輸入姓名"
      />
      <p className="mt-1 text-xs text-slate-500">目前 {name.length} 字</p>
    </label>
  );
}

這段程式展示受控元件的三件事:第一,value={name} 把 state 綁到欄位;第二,onChange={(e) => setName(e.target.value)} 把使用者的輸入寫回 state;第三,每次 state 改變,元件 re-render,欄位值同步更新。這條鏈是「單向資料流」的標準模式:state → value,使用者輸入 → onChange → setState → state。

把多個欄位組合成表單:

// src/components/BookingForm.tsx(基礎版)
// 受控表單:所有欄位都住在 React state
import { useState } from "react";

type FormState = {
  name: string;
  email: string;
  date: string;
  time: string;
  note: string;
};

const empty: FormState = {
  name: "",
  email: "",
  date: "",
  time: "",
  note: "",
};

export function BookingForm() {
  const [form, setForm] = useState<FormState>(empty);

  // 通用更新函式:傳入欄位名與新值,更新 form 的某一格
  const update = <K extends keyof FormState>(key: K, value: FormState[K]) => {
    setForm((prev) => ({ ...prev, [key]: value }));
  };

  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={form.name}
          onChange={(e) => update("name", 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">Email</span>
        <input
          type="email"
          value={form.email}
          onChange={(e) => update("email", 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>
        <input
          type="date"
          value={form.date}
          onChange={(e) => update("date", 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>
        <select
          value={form.time}
          onChange={(e) => update("time", e.target.value)}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        >
          <option value="">請選擇</option>
          <option value="morning">上午(09:00–12:00)</option>
          <option value="afternoon">下午(13:00–17:00)</option>
          <option value="evening">晚上(18:00–21:00)</option>
        </select>
      </label>

      <label className="block">
        <span className="text-sm font-medium text-slate-700">備註</span>
        <textarea
          value={form.note}
          onChange={(e) => update("note", e.target.value)}
          rows={3}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        />
      </label>
    </form>
  );
}

這個範例把五個欄位(姓名、Email、日期、時段、備註)全部用受控元件管理。注意 update 函式用了泛型 K extends keyof FormState,這樣 TypeScript 能在編譯期檢查「欄位名稱必須是 FormState 的鍵」、「值的型別必須符合該欄位」,不會打錯字。受控元件的缺點在這裡很明顯:每個欄位都要寫 value 與 onChange,五個欄位就重複了五次。當欄位數增加到 20 個時,這種重複會非常囉嗦,這就是後面 React Hook Form 出場的原因。

非受控元件:用 ref 讀欄位值

非受控元件讓瀏覽器保留欄位的值,React 只在需要時才從 DOM 讀出來。最常見的情境是「檔案上傳」(input type="file" 一定要用 ref)或「不需要即時知道值的欄位」。下面是基本寫法:

// src/components/FileUploadField.tsx
// 非受控元件:檔案上傳一定要用 ref
import { useRef } from "react";

export function FileUploadField() {
  const fileRef = useRef<HTMLInputElement>(null);

  const handleSubmit = () => {
    // 送出時才從 DOM 讀取檔案
    const file = fileRef.current?.files?.[0];
    if (!file) return;
    console.log("選擇的檔案:", file.name, file.size);
  };

  return (
    <div className="space-y-2">
      <input ref={fileRef} type="file" accept="image/*" className="text-sm" />
      <button
        type="button"
        onClick={handleSubmit}
        className="rounded bg-slate-900 px-3 py-1 text-sm text-white"
      >
        上傳
      </button>
    </div>
  );
}

useRef 回傳一個物件,這個物件的 .current 指向對應的 DOM 節點。React 在元件掛載時把 fileRef.current 設定為 input DOM 節點,從畫面移除時清掉。檔案上傳的 API 必須讀取 DOM 才能拿到 File 物件,所以這是非受控元件的典型情境——檔案無法被 React 「控制」成 state,因為 FileList 是唯讀的瀏覽器物件。

非受控元件在「大量欄位但不需即時驗證」時也有用。例如一個問卷調查有 50 題選擇題,每題都即時 re-render 太浪費,這時可以全部用非受控,送出時再用 FormData 一次讀完。實務上這類情境會直接用 React Hook Form,它的底層用了非受元件機制但提供更友善的 API。

用 Zod 做欄位驗證

Zod 是 TypeScript 生態最常用的 schema 驗證函式庫。它的優點是「型別與驗證一次寫完」:寫完 schema 後,可以從 schema 推導出 TypeScript 型別,也可以在執行期驗證資料。Day 10 我們用過一次,這次在表單上深入。

// src/schemas/booking.ts
// 預約表單的 Zod schema
import { z } from "zod";

export const BookingSchema = z.object({
  name: z.string().min(2, "姓名至少 2 個字").max(50, "姓名最多 50 個字"),
  email: z.string().email("Email 格式不正確"),
  date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "請選擇有效日期"),
  time: z.enum(["morning", "afternoon", "evening"], {
    errorMap: () => ({ message: "請選擇時段" }),
  }),
  note: z.string().max(500, "備註最多 500 字").optional(),
});

export type BookingInput = z.infer<typeof BookingSchema>;

這個 schema 把每個欄位的驗證規則寫在一起。「姓名至少 2 個字」、「Email 格式正確」、「日期是 YYYY-MM-DD」、「時段是三選一」、「備註最多 500 字」這些條件全部用 Zod 描述。執行驗證時,Zod 會回傳一個結果物件,裡面有「是否通過」與「每個欄位的錯誤訊息」。

// src/utils/validate.ts
// 驗證工具:把 Zod 的結果轉成「欄位名 → 錯誤訊息」的物件
import { z } from "zod";

export type FieldErrors = Record<string, string>;

export function validateWithZod<T>(schema: z.ZodType<T>, data: unknown): {
  ok: boolean;
  data?: T;
  errors: FieldErrors;
} {
  const result = schema.safeParse(data);
  if (result.success) {
    return { ok: true, data: result.data, errors: {} };
  }
  const errors: FieldErrors = {};
  for (const issue of result.error.issues) {
    const path = issue.path.join(".");
    if (!errors[path]) errors[path] = issue.message;
  }
  return { ok: false, errors };
}

這個工具函式把 Zod 的驗證結果轉成前端表單常用的「以欄位名為鍵」的錯誤物件。Zod 預設回傳的是巢狀的 issues 陣列,這個轉換讓元件可以直接 {errors.email &&

{errors.email}

} 顯示錯誤。

React Hook Form:把受控元件的繁瑣交給函式庫

前面示範的純受控表單有兩個問題:第一,每個欄位都要寫 value 與 onChange,欄位多時非常囉嗦;第二,每次打字都 re-render 整個元件,對 20 欄位以上的表單有效能疑慮。React Hook Form(簡稱 RHF)解決了這兩個問題:它用 ref 控制欄位(避免 re-render)、用 register 函式取代 value/onChange、用 handleSubmit 包裝送出邏輯。

// src/components/BookingFormRHF.tsx
// 用 React Hook Form 改寫預約表單
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { BookingSchema, type BookingInput } from "../schemas/booking";

export function BookingFormRHF({ onSubmit }: { onSubmit: (data: BookingInput) => Promise<void> }) {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    reset,
  } = useForm<BookingInput>({
    resolver: zodResolver(BookingSchema),
    defaultValues: { name: "", email: "", date: "", time: undefined, note: "" },
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)} 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
          {...register("name")}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        />
        {errors.name && <p className="mt-1 text-xs text-red-600">{errors.name.message}</p>}
      </label>

      <label className="block">
        <span className="text-sm font-medium text-slate-700">Email</span>
        <input
          type="email"
          {...register("email")}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        />
        {errors.email && <p className="mt-1 text-xs text-red-600">{errors.email.message}</p>}
      </label>

      <label className="block">
        <span className="text-sm font-medium text-slate-700">日期</span>
        <input
          type="date"
          {...register("date")}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        />
        {errors.date && <p className="mt-1 text-xs text-red-600">{errors.date.message}</p>}
      </label>

      <label className="block">
        <span className="text-sm font-medium text-slate-700">時段</span>
        <select
          {...register("time")}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        >
          <option value="">請選擇</option>
          <option value="morning">上午(09:00–12:00)</option>
          <option value="afternoon">下午(13:00–17:00)</option>
          <option value="evening">晚上(18:00–21:00)</option>
        </select>
        {errors.time && <p className="mt-1 text-xs text-red-600">{errors.time.message}</p>}
      </label>

      <label className="block">
        <span className="text-sm font-medium text-slate-700">備註</span>
        <textarea
          {...register("note")}
          rows={3}
          className="mt-1 w-full rounded border border-slate-300 p-2 text-sm"
        />
        {errors.note && <p className="mt-1 text-xs text-red-600">{errors.note.message}</p>}
      </label>

      <div className="flex gap-2">
        <button
          type="submit"
          disabled={isSubmitting}
          className="rounded bg-slate-900 px-4 py-2 text-sm text-white disabled:opacity-50"
        >
          {isSubmitting ? "送出中…" : "建立預約"}
        </button>
        <button
          type="button"
          onClick={() => reset()}
          className="rounded bg-slate-200 px-4 py-2 text-sm hover:bg-slate-300"
        >
          清空
        </button>
      </div>
    </form>
  );
}

這段程式把前面的純受控表單換成 React Hook Form 版本。差別有四點:第一,{...register("name")} 一行就把 value、onChange、ref、name 全部接好;第二,resolver: zodResolver(BookingSchema) 讓驗證交給 Zod,errors 直接從 formState 拿;第三,isSubmitting 是內建狀態,送出期間自動為 true;第四,reset() 一行清空所有欄位。

效能上,React Hook Form 用 ref 而非 state 控制欄位,使用者打字時不會觸發整個表單 re-render,只有「訂閱了那個欄位的元件」會更新。對 20 欄位以上的表單,這個差異很明顯。除了效能之外,React Hook Form 還提供 watch(即時觀察欄位值)、setValue(程式化更新欄位)、trigger(手動觸發驗證)、formState.isDirty(使用者是否改過欄位)等豐富 API,這些都是純 useState 寫起來很囉嗦的功能。

整合:表單送出 + 登入檢查 + API 呼叫

把昨天學的 AuthProvider 與今天的表單接起來。完整流程:使用者填好表單 → 檢查是否登入 → 未登入就跳轉到登入頁 → 已登入就呼叫 API → 成功就清空表單、顯示成功訊息;失敗就保留輸入、顯示錯誤訊息。

// src/pages/NewBookingPage.tsx
// 「建立預約」頁面:表單 + 登入檢查 + API 呼叫
import { useState } from "react";
import { useAuth } from "../contexts/AuthContext";
import { BookingFormRHF } from "../components/BookingFormRHF";
import type { BookingInput } from "../schemas/booking";

export function NewBookingPage() {
  const { state } = useAuth();
  const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null);

  // 登入檢查:未登入就不渲染表單
  if (!state.user) {
    return (
      <div className="mx-auto max-w-xl p-8">
        <p className="text-slate-600">請先登入才能建立預約。</p>
        <a href="/login" className="mt-2 inline-block text-blue-600 underline">前往登入</a>
      </div>
    );
  }

  const onSubmit = async (data: BookingInput) => {
    try {
      const res = await fetch("/api/bookings", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(data),
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body.message ?? `HTTP ${res.status}`);
      }
      setResult({ ok: true, message: "預約建立成功!" });
    } catch (err) {
      // 失敗時保留輸入,只顯示錯誤
      setResult({ ok: false, message: err instanceof Error ? err.message : "送出失敗" });
    }
  };

  return (
    <main className="min-h-screen bg-slate-50 p-4">
      <h1 className="mb-4 text-2xl font-bold">建立預約</h1>
      <BookingFormRHF onSubmit={onSubmit} />
      {result && (
        <p className={`mt-3 text-sm ${result.ok ? "text-green-600" : "text-red-600"}`}>
          {result.message}
        </p>
      )}
    </main>
  );
}

這個頁面把「受控檢查 → 送出 → 結果顯示」三件事串起來。注意失敗時不呼叫 reset(),這樣使用者的輸入會保留在表單裡,只需修正錯誤欄位即可重送。這是「儲存失敗時保留輸入」這個核心紀律的實踐——絕對不要在失敗時把使用者的辛苦輸入清掉。

常見錯誤與踩雷

第一個踩雷是「忘了設 type="email"」。HTML 的 input type 預設是 text,如果你忘了設 type="email",瀏覽器就不會自動擋掉非 Email 格式,使用者可以輸入「abc」,使用者送出時 Zod 才會跳出「Email 格式不正確」。這雖然能驗證,但 UX 不佳——使用者多按一次送出按鈕才看到錯誤。修法是善用 HTML 原生 type 與瀏覽器內建驗證。

第二個踩雷是「用 value 卻沒設 onChange」。React 會跳出警告「You provided a value prop to a form field without an onChange handler」,並把欄位鎖成唯讀。這在「想設初始值但讓使用者改」時特別常見,例如 input 標籤寫成 value 加初始值但沒給 onChange。修法是用 defaultValue={initial}(非受控)或補上 onChange(受控)。

第三個踩雷是「在 onChange 裡直接呼叫 API」。最常見的寫法是「使用者一邊打字一邊打搜尋 API」,結果每打一個字就送一次請求。這就是昨天 Day 10 教的 useDebounce 的典型應用情境——把受控值先 debounce 再送去打 API,避免伺服器被轟炸。

第四個踩雷是「useState 用 object 卻沒處理不可變更新」。例如 setForm(form); form.name = newName; 這種直接改動原物件的寫法,React 不會偵測到變化、不會 re-render。修法是用展開運算子:setForm({ ...form, name: newName }),或是 React Hook Form 的 setValue。

第五個踩雷是「忘記處理送出中的重複點擊」。使用者可能連續點「送出」按鈕好幾次,導致同一筆預約被建立多次。修法是把按鈕 disabled={isSubmitting},或在 onSubmit 開頭檢查「是否正在送出」。React Hook Form 的 isSubmitting 已經幫你處理這件事,但要記得在按鈕上設 disabled。

效能與實務提醒

受控元件的最大成本是「每次輸入都 re-render」。對小型表單(5 欄位以下)影響微乎其微;對中型表單(10–20 欄位)可以用 React.memo 包子元件降低影響;對大型表單(50 欄位以上)就該用 React Hook Form 或非受控元件。實務上的判斷指標是「打開 React DevTools 的 Profiler,看打字期間哪些元件 re-render 次數最高」。

另一個實務提醒是「表單送出後的清理」。成功的送出應該清空表單(呼叫 reset())、顯示成功訊息、3 秒後導向其他頁面。失敗的送出應該保留輸入、顯示錯誤訊息、讓使用者馬上可以重送。這兩種情境要分清楚,避免「送失敗卻把表單清空」這種 UX 慘案。

最後是「無障礙」考量。表單欄位應該用 label 元素包住輸入框或用 htmlFor 綁定,這樣螢幕閱讀器才能正確朗讀欄位名稱。錯誤訊息應該用 aria-invalid 與 aria-describedby 綁定,這樣輔助科技能朗讀「此欄位有錯誤」。Day 28「無障礙」會再展開這個主題。

另一個團隊協作的實務是「把表單欄位包成可控元件」。如果同一個專案有多個表單(例如「建立預約」、「編輯預約」、「建立使用者」),它們的「姓名」、「Email」、「日期」欄位驗證規則與外觀都很類似。這時可以包成 TextField、EmailField、DateField 等元件,每個元件內部處理自己的受控邏輯與錯誤顯示,外部只要傳 props。這樣有三個好處:第一,欄位樣式統一,不會出現「A 表單的姓名欄跟 B 表單的姓名欄長得不一樣」;第二,驗證邏輯集中管理,未來想改「姓名最少 3 字」只要改一個地方;第三,新表單的開發速度變快,只要組合現有欄位元件即可。

最後是「測試」。表單是元件測試最常見的場景之一。Day 15 會教 Vitest 與 Testing Library 怎麼測表單:模擬使用者輸入(userEvent.type)、觸發驗證(screen.getByText 找錯誤訊息)、驗證 API 呼叫(mock fetch)。良好的表單設計應該讓測試寫起來很直覺——把所有 UI 邏輯放在元件、把資料邏輯放在 Hook、API 呼叫透過 props 注入,這樣測試就能在不真的打 API 的情況下驗證整個流程。

小結

今天我們把 React 表單從受控/非受控元件、Zod 驗證、React Hook Form、整合 Context、處理送出結果一次走完。重點回顧:受控元件用 value + onChange 讓 React 控制欄位值,優點是即時驗證、條件式行為;非受控元件用 ref 讓瀏覽器保留值,適合檔案上傳與大型表單;Zod schema 同時提供型別與執行期驗證,搭配 zodResolver 可直接接到 React Hook Form;失敗時保留輸入、成功時清空並導向,是表單 UX 的基本紀律。我們也用 BookingForm 範例把「受控元件 → Zod → React Hook Form → 整合 Context → 處理送出結果」這條完整的鏈演練了一次,這個模式會在 Day 34「預約流程與表單」與 Day 38「登入流程」繼續使用。明天 Day 13 會進入元件設計原則——組合、抽象、邊界,討論什麼時候該拆元件、什麼時候不該拆、props 怎麼設計、children 怎麼傳,以及容器元件與展示元件的差別,這是「寫出好維護 React 程式」的下一個關鍵能力。我們會用一個「預約卡片」元件當範例,從「全部寫在一個元件」演化到「拆成 Header、Body、Actions 三個子元件」,並且親自感受「過度拆解」帶來的程式碼膨脹。

結語

明天,我們會把焦點從「元件內部邏輯」轉到「元件之間的關係」。Day 13 會探討元件設計的三大原則——單一職責、組合優於繼承、props 的最小介面;並用一個完整的「預約卡片」元件當範例,從「全部寫在一個元件」演化到「拆成 Header / Body / Actions 三個子元件」,讓你體會「什麼時候該拆、什麼時候不該拆」的取捨。記得今天的 BookingFormRHF 先留著,明天會把它內部的「卡片本體」與「動作按鈕」拆成可組合的小元件。

延伸資源

  • React 官方〈Forms〉(19.x,2026 年 3 月):https://react.dev/reference/react-dom/components/form
  • React Hook Form 官方文件(最新版):https://react-hook-form.com/
  • Zod 官方說明:https://zod.dev/
  • MDN〈HTMLInputElement〉:https://developer.mozilla.org/zh-TW/docs/Web/API/HTMLInputElement
  • WebAIM〈Creating Accessible Forms〉:https://webaim.org/techniques/forms/

留言

這個網誌中的熱門文章

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