本文 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 不等同於 ReactNodeReactNode 包含了 JSX.Elementstringnumbernullundefinedboolean 以及陣列等更廣義的型別。
  • 因此,在只允許傳入「真正的 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 元素,避免把 voidstring 誤傳。


範例 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 渲染。


常見陷阱與最佳實踐

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

最佳實踐

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

實際應用場景

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

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

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