技術 5 分鐘閱讀

React + TypeScript 開發心得:型別安全的真正價值

React + TypeScript 開發心得:型別安全的真正價值

前言

最近把手邊的幾個 React 專案都升級成 TypeScript 了。一開始覺得多寫型別很麻煩,但用了幾個月後,我已經無法想像不用 TypeScript 的日子。

為什麼要用 TypeScript?

編譯時就抓到錯誤

沒有 TypeScript 的時候,很多 bug 要到瀏覽器跑起來才會發現。TypeScript 把錯誤提前到編輯器裡:

// ❌ TypeScript 會直接報錯
interface Product {
  id: number;
  name: string;
  price: number;
}

function formatPrice(product: Product) {
  return product.prce; // 拼錯了!TS 會提示
}

IDE 自動補全

定義好型別後,VS Code 的自動補全變得超級精準,開發速度反而更快了。

React 常用型別模式

Props 定義

interface ButtonProps {
  label: string;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
  onClick: () => void;
}

function Button({ label, variant = 'primary', disabled, onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

useState 型別

interface User {
  id: number;
  name: string;
  email: string;
}

const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<Product[]>([]);

Event Handler 型別

function SearchBar() {
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
    </form>
  );
}

API 回應型別

interface ApiResponse<T> {
  data: T;
  meta: {
    current_page: number;
    last_page: number;
    total: number;
  };
}

async function fetchProducts(): Promise<ApiResponse<Product[]>> {
  const response = await fetch('/api/products');
  return response.json();
}

與 Laravel 搭配的型別同步

搭配 Inertia.js 使用時,可以用套件自動從 Laravel 產生 TypeScript 型別:

npm install -D @tightenco/ziggy

也可以手動維護一個 types.d.ts

// resources/js/types.d.ts
export interface PageProps {
  auth: {
    user: User | null;
  };
  flash: {
    message?: string;
  };
}

常見的 TypeScript 地雷

不要濫用 any

// ❌ 不好
const data: any = fetchData();

// ✅ 正確
const data: Product[] = await fetchProducts();

善用 Utility Types

// 讓所有欄位變成 optional
type ProductFormData = Partial<Product>;

// 只挑選需要的欄位
type ProductPreview = Pick<Product, 'name' | 'price'>;

// 排除某些欄位
type CreateProduct = Omit<Product, 'id' | 'created_at'>;

搭配好用的工具

推薦在 VS Code 安裝以下外掛加強 TypeScript 體驗:

  • Pretty TypeScript Errors:讓錯誤訊息更易讀
  • TypeScript Importer:自動匯入型別

小結

目前覺得 TypeScript 前期投入的學習成本,在中長期絕對值回票價。特別是接案的場景,型別安全能大幅降低交付後的 bug 數量,讓客戶對你更有信心。

如果你需要類似的系統,歡迎聯繫討論。

#React #TypeScript
Thousand Lab

Thousand Lab