本文 AI 產出,尚未審核

TypeScript 課程:React + TypeScript(實務應用)— JSX 型別定義(JSX.Element)


簡介

在使用 React 開發前端介面時,JSX 已成為最常見的語法糖;而在 TypeScript 專案中,正確地描述 JSX 的型別是提升開發效率與避免執行時錯誤的關鍵。
JSX.Element 是 TypeScript 為 React 元素所提供的基本型別,了解它的結構與使用方式,能讓我們在撰寫元件、傳遞子元素、或是建立高階組件(HOC)時得到完整的型別推斷與 IDE 提示。

本篇文章將從 概念說明、實作範例、常見陷阱,到 實務應用,一步步帶你掌握 JSX.Element 的正確寫法,讓你的 React + TypeScript 專案更安全、更可維護。


核心概念

1. JSX.Element 是什麼?

  • JSX.Element 代表 React 元素,亦即 React.createElement(...) 的回傳值。
  • 在 TypeScript 中,JSX.Element 的定義大致如下(簡化版):
type JSXElement = {
  type: string | FunctionComponent<any> | ComponentClass<any>;
  props: any;
  key: string | number | null;
};
  • 注意:JSX.Element 不等同於 ReactNode。ReactNode 包含了 JSX.Element、string、number、null、undefined、boolean 以及陣列等更廣義的型別。
  • 因此,在只允許傳入「真正的 React 元素」的情境下,我們會使用 JSX.Element;而在允許文字、陣列等多元輸出的情況下,則使用 ReactNode。

2. 為什麼要明確宣告 JSX.Element?

  • 型別安全:避免把非 React 元素(例如 string)傳給只接受元件的函式。
  • IDE 補全:正確的型別讓 VSCode、WebStorm 等編輯器提供屬性提示與錯誤檢查。
  • 文件化:在專案文件或元件 API 說明中,使用 JSX.Element 能清楚表達「此參數必須是 React 元素」的意圖。

3. 常見的型別宣告方式

場景 型別 範例
元件的 children 必須是單一元素 JSX.Element children: JSX.Element
元件接受任意子節點(文字、陣列…) ReactNode children: React.ReactNode
高階組件返回的元件類型 JSX.Element function withLogging<P>(Component: React.ComponentType<P>): React.FC<P>
直接在函式中回傳 JSX JSX.Element function renderHeader(): JSX.Element

程式碼範例

以下範例示範在不同情境下如何使用 JSX.Element,並附上說明註解。

範例 1:單一子元素的 children 型別

import React from 'react';

type CardProps = {
  /** 必須傳入一個 React 元素作為卡片內容 */
  children: JSX.Element;
};

const Card: React.FC<CardProps> = ({ children }) => {
  return (
    <div className="card">
      {/* 直接渲染傳入的 JSX.Element */}
      {children}
    </div>
  );
};

// 使用範例
<Card>
  {/* 這裡必須是單一的 JSX.Element,不能是文字或陣列 */}
  <h2>卡片標題</h2>
</Card>;

說明:若 children 被宣告為 JSX.Element,傳入 <Card> 時只能提供單一的 React 元素;若想允許文字或多個子元素,應改用 ReactNode。


範例 2:函式回傳 JSX.Element

/**
 * 產生一個簡易的訊息提示元件
 * @param message 要顯示的文字
 * @returns JSX.Element
 */
function Alert(message: string): JSX.Element {
  return (
    <div className="alert">
      <strong>注意:</strong> {message}
    </div>
  );
}

// 在其他元件中使用
const App: React.FC = () => {
  return (
    <section>
      {Alert('系統維護中,請稍後再試。')}
    </section>
  );
};

說明:將 Alert 定義為回傳 JSX.Element,讓 TypeScript 知道此函式永遠會產生一個 React 元素,避免把 void 或 string 誤傳。


範例 3:高階組件(HOC)返回 JSX.Element

import React from 'react';

/**
 * 為傳入的元件加上 console.log 的 HOC
 * @param WrappedComponent 任意 React 元件
 */
function withLogger<P>(WrappedComponent: React.ComponentType<P>) {
  const ComponentWithLogger: React.FC<P> = (props) => {
    console.log('Render with props:', props);
    // 使用 React.createElement 仍會得到 JSX.Element
    const element = <WrappedComponent {...props} />;
    return element; // 型別為 JSX.Element
  };

  // 為了讓外部知道回傳的是元件,使用 React.FC<P>
  return ComponentWithLogger;
}

// 原始元件
const Hello: React.FC<{ name: string }> = ({ name }) => <p>Hello, {name}!</p>;

// 包裝後的元件
const HelloWithLog = withLogger(Hello);

// 使用
<HelloWithLog name="Alice" />;

說明:withLogger 內部建立的 element 型別自動推斷為 JSX.Element。對外回傳的仍是 React 元件,但在實作時,我們可以安心使用 JSX.Element 作為暫存或回傳值。


範例 4:條件渲染與 JSX.Element | null

type ConditionalProps = {
  show: boolean;
};

const ConditionalRender: React.FC<ConditionalProps> = ({ show }) => {
  // 依照條件回傳 JSX.Element 或 null
  const content: JSX.Element | null = show ? (
    <p>顯示的內容</p>
  ) : null;

  return <section>{content}</section>;
};

說明:在條件渲染時,常見的型別是 JSX.Element | null,因為 null 代表「什麼都不渲染」。若只寫 JSX.Element,TS 會報錯。


範例 5:使用 React.cloneElement 時的型別

type CloneProps = {
  /** 必須是可被 clone 的 React 元素 */
  element: JSX.Element;
};

const CloneWrapper: React.FC<CloneProps> = ({ element }) => {
  // cloneElement 會回傳同樣的 JSX.Element
  const cloned = React.cloneElement(element, { className: 'cloned' });
  return cloned; // 型別仍為 JSX.Element
};

說明:React.cloneElement 的回傳型別為 React.ReactElement<any, any>,它與 JSX.Element 兼容,可直接作為 JSX 渲染。


常見陷阱與最佳實踐

陷阱 說明 解決方式
把 ReactNode 當 JSX.Element 使用 ReactNode 包含 string、number,直接指定 JSX.Element 會導致型別錯誤。 若允許文字或陣列,改用 React.ReactNode;若只能是元素,保留 JSX.Element。
忘記 null 或 undefined 的型別 條件渲染常回傳 null,但型別只寫 JSX.Element 會產生錯誤。 使用聯合型別 `JSX.Element
在泛型元件中未正確傳遞 JSX.Element 泛型若寫成 T extends JSX.Element,實際傳入 null 會失敗。 只在需要限制為「一定是元素」時使用;否則使用 ReactNode。
過度使用 any 失去型別保護 為了快速解決錯誤,直接把參數改成 any。 盡量使用具體型別,如 JSX.Element、ReactElement<any, any>,或使用 React.ComponentPropsWithoutRef<typeof Component> 取得正確屬性型別。
忘記 key 的型別 在陣列渲染時,key 必須是 `string number,但 JSX.Element本身不保證有key`。

最佳實踐

  1. 明確區分 JSX.Element 與 ReactNode:只在「必須是 React 元素」的 API 中使用 JSX.Element。
  2. 條件渲染時加入 null:const result: JSX.Element | null = condition ? <Comp/> : null;。
  3. 在自訂 Hook 中返回 JSX:若 Hook 需要返回 UI,使用 JSX.Element 而非 any。
  4. 使用 React.FC 或 React.ComponentType 搭配泛型,讓傳入的子元素型別自動推斷。
  5. 利用 React.cloneElement 或 React.isValidElement 做安全檢查,避免把非元素傳入需要 JSX.Element 的函式。

實際應用場景

1. 建立 UI 套件庫(Component Library)

在開發像是 Material‑UI、Ant Design 這類套件時,元件的 children 必須是 合法的 React 元素,才能保證樣式與行為的正確性。此時,對外 API 常會寫成:

export interface ModalProps {
  /** 僅接受單一根元素作為內容 */
  children: JSX.Element;
}

這樣使用者若誤傳入文字或陣列,編譯階段就會直接報錯,提升套件的健全度。

2. 高階組件(HOC)與 Render Props

在實作 權限檢查 HOC 或 資料抓取 Render Props 時,我們常需要把子元素作為參數傳入,並在內部返回新元素:

function withAuth<P>(Component: React.ComponentType<P>) {
  return (props: P): JSX.Element => {
    const isLoggedIn = useAuth();
    return isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />;
  };
}

return 的型別明確為 JSX.Element,讓 TypeScript 能正確推斷 withAuth 的回傳型別。

3. 自訂 Hook 回傳 UI

有時我們會把 UI 抽成 Hook,例如 useLoadingOverlay:

function useLoadingOverlay(isLoading: boolean): JSX.Element | null {
  return isLoading ? (
    <div className="overlay">Loading...</div>
  ) : null;
}

使用者在組件中直接寫 {useLoadingOverlay(isFetching)},型別安全且可讀性佳。

4. 渲染函式(Render Function)作為 Prop

type ListProps<T> = {
  items: T[];
  /** 渲染每一筆資料的函式,必須回傳 JSX.Element */
  renderItem: (item: T, index: number) => JSX.Element;
};

function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((it, i) => (
        <li key={i}>{renderItem(it, i)}</li>
      ))}
    </ul>
  );
}

renderItem 的回傳型別使用 JSX.Element,確保每筆資料都會產生合法的 React 元素。


總結

  • JSX.Element 是 React 元素的型別,專門用來描述「必須是 JSX」的情境。
  • 正確區分 JSX.Element 與 ReactNode,能避免許多型別衝突與執行時錯誤。
  • 在 單一子元素、函式回傳、HOC、Render Props 等常見模式中,使用 JSX.Element 能獲得最佳的型別安全與 IDE 支援。
  • 留意 條件渲染、null/undefined、以及 any 的濫用,遵循「盡可能具體」的原則。
  • 在實務開發(套件庫、權限檢查、Hook、Render Function)中,將 JSX.Element 作為 API 的型別定義,是提升程式碼可讀性、可維護性與錯誤預防的關鍵。

掌握了 JSX.Element 的概念與寫法後,你的 React + TypeScript 專案將會變得更安全、可預測,也能在團隊協作時提供更清晰的型別契約。祝你在實務開發中玩得開心,寫出高品質的 TypeScript React 程式碼!