技術筆記 / JavaScript / TypeScript

[React + TypeScript] 掌握資料渲染:型別安全的 List 與 Dict 處理指南

在 React 開發中,處理 List (Array) 和 Dictionary (Object) 是基本功。而在 TypeScript 的加持下,我們可以明確定義這些資料的「形狀 (Shape)」,讓 IDE 告訴我們屬性是否存在,而不是等到瀏覽器報錯才發現拼錯字。

這篇文章將帶你了解如何在 TS 環境下優雅地渲染資料。


1. 處理 List (Array):明確定義陣列內容

在 TypeScript 中,我們習慣先告訴元件這個陣列裡裝的是什麼(字串?數字?還是物件?)。

基礎範例:字串陣列 (string[])

import React from 'react';

// 定義資料型別:這是一個字串陣列
const fruits: string[] = ["Apple", "Banana", "Cherry", "Durian"];

export const FruitList: React.FC = () => {
  return (
    <div className="p-4 border rounded">
      <h3>Fruit List</h3>
      <ul>
        {/* map 的用法不變,但現在 TS 知道 'fruit' 是一個 string */}
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
};

2. 處理 Dictionary (Object):Interface 是好朋友

TypeScript 最強大的地方在於 interface 或 type。當你定義好物件結構後,如果你試圖讀取一個不存在的屬性(例如把 role 拼成 roll),TS 會直接在編輯器畫紅線警告你。

步驟一:定義 Interface

// 1. 定義物件的形狀
interface UserProfile {
  name: string;
  role: string;
  level: number;
  isActive: boolean;
}

const currentUser: UserProfile = {
  name: "Gemini",
  role: "AI Assistant",
  level: 99,
  isActive: true
};

步驟二:渲染 (與 Props 結合)

// 定義 Component 的 Props 型別
interface UserCardProps {
  user: UserProfile;
}

export const UserCard: React.FC<UserCardProps> = ({ user }) => {
  return (
    <div className="card">
      {/* TS 會提示你有 name, role, level 這些屬性可用 */}
      <h2>{user.name}</h2>
      <p>Role: {user.role}</p>
      <p>Status: {user.isActive ? "Online" : "Offline"}</p>
    </div>
  );
};

進階:動態遍歷物件 (Record Type)

如果你要用 Object.entries 遍歷一個物件,通常適用於該物件屬性不固定,但類型統一的情況(例如設定檔)。

// Record<KeyType, ValueType> 
// 這裡表示 Key 是字串,Value 可能是 string 或 boolean
const settings: Record<string, string | boolean> = {
  theme: "Dark",
  notifications: true,
  privacy: "Public"
};

export const SettingsList: React.FC = () => {
  return (
    <dl>
      {Object.entries(settings).map(([key, value]) => (
        <div key={key}>
          <dt>{key}:</dt>
          {/* 因為 value 可能是 boolean,React 不會渲染 boolean,需轉字串 */}
          <dd>{value.toString()}</dd>
        </div>
      ))}
    </dl>
  );
};

3. 實戰場景:List of Objects (物件陣列)

這是最常見的 API 回傳格式。我們通常會定義一個 interface 來描述單筆資料。

// 1. 定義單篇文章的資料結構
interface Post {
  id: number;
  title: string;
  content: string;
}

const posts: Post[] = [
  { id: 101, title: "React 101", content: "Learn the basics..." },
  { id: 102, title: "Typescript Tips", content: "Interfaces are cool..." },
];

最佳實踐:拆分元件並定義 Props

這展示了 TS 如何保護元件的邊界。如果父元件傳入錯誤的 props,程式碼根本編譯不過。

// 2. 子元件:定義它需要什麼 Props
// 這裡可以直接使用 Post interface,或者用 Pick 取出需要的欄位
interface PostItemProps {
  title: string;
  content: string;
}

const PostItem: React.FC<PostItemProps> = ({ title, content }) => (
  <div style={{ marginBottom: '20px' }}>
    <h3>{title}</h3>
    <p>{content}</p>
  </div>
);

// 3. 主列表元件
export const BlogList: React.FC = () => {
  return (
    <section>
      <h1>Latest Posts</h1>

      {posts.map((post) => (
        <PostItem 
          key={post.id} 
          title={post.title}
          content={post.content}
          // 如果這裡少傳了 content,TS 會立刻報錯
        />
      ))}
    </section>
  );
};

TypeScript 重點筆記

概念 寫法 為什麼要這樣寫?
定義資料 interface User { name: string } 讓編輯器知道 user. 後面可以接什麼。
陣列型別 User[] 或 Array<User> 確保你不會把 string 塞進 number 陣列。
Component React.FC<PropsType> 定義元件接收的 Props 格式,防止傳錯參數。
Map 參數 posts.map((post) => ...) 因為 posts 已經定義為 Post[],這裡的 post 會自動推斷為 Post 型別,不需重寫。

下一步你可以做什麼?

既然你已經使用了 TypeScript,通常下一步我們會遇到「非同步資料的型別定義」。

如果您有興趣,我可以示範如何定義 API 回傳結果的 Interface,並使用 useEffect 抓取資料後,將這些 JSON 資料安全地放入 TS 的狀態 (useState<Post[]>) 中。這樣能完整模擬真實開發流程。

在 GitHub 看原文