使用 TypeScript

TypeScript 是一種流行的方式,可以將類型定義添加到 JavaScript 程式碼庫。TypeScript 原生支援 JSX,您可以透過將 @types/react@types/react-dom 新增到您的專案中,以獲得完整的 React Web 支援。

安裝

所有 正式環境等級的 React 框架 都支援使用 TypeScript。請遵循框架特定的安裝指南

將 TypeScript 加入現有 React 專案

要安裝最新版本的 React 類型定義

終端機
npm install @types/react @types/react-dom

需要在您的 tsconfig.json 中設定以下編譯器選項

  1. dom 必須包含在 lib 中(注意:如果沒有指定 lib 選項,則預設包含 dom)。
  2. jsx 必須設定為其中一個有效選項。preserve 應該足以應付大多數應用程式。如果您要發佈程式庫,請參考 jsx 文件 以瞭解要選擇哪個值。

React 元件搭配 TypeScript

注意事項

每個包含 JSX 的檔案都必須使用 .tsx 副檔名。這是一個 TypeScript 特定的副檔名,告訴 TypeScript 這個檔案包含 JSX。

使用 TypeScript 撰寫 React 與使用 JavaScript 撰寫 React 非常相似。使用元件時的關鍵差異在於您可以為元件的 props 提供類型。這些類型可以用於正確性檢查,並在編輯器中提供內嵌文件。

MyButton 元件快速入門 指南中,我們可以新增一個類型來描述按鈕的 title

function MyButton({ title }: { title: string }) {
  return (
    <button>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="I'm a button" />
    </div>
  );
}

注意事項

這些沙盒可以處理 TypeScript 程式碼,但它們不會執行類型檢查器。這表示您可以修改 TypeScript 沙盒來學習,但您不會收到任何類型錯誤或警告。要進行類型檢查,您可以使用TypeScript Playground 或使用功能更完整的線上沙盒。

這種內聯語法是為元件提供類型的最簡單方法,但是一旦您開始要描述幾個欄位,它就會變得難以處理。您可以改用 interfacetype 來描述元件的 props。

interface MyButtonProps {
  /** The text to display inside the button */
  title: string;
  /** Whether the button can be interacted with */
  disabled: boolean;
}

function MyButton({ title, disabled }: MyButtonProps) {
  return (
    <button disabled={disabled}>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="I'm a disabled button" disabled={true}/>
    </div>
  );
}

描述元件 props 的類型可以根據您的需要簡單或複雜,但它們應該是以 typeinterface 描述的物件類型。您可以在物件類型中瞭解 TypeScript 如何描述物件,但您可能也對使用聯集類型來描述可以是幾種類型之一的 prop 感興趣,以及從類型建立類型指南,以瞭解更進階的用例。

範例 Hooks ...

@types/react 中的類型定義包含內建 Hooks 的類型,因此您可以在元件中使用它們,而無需任何額外設定。它們的建置目的是要考慮您在元件中編寫的程式碼,因此您在很多時候都會得到推斷類型,理想情況下不需要處理提供類型的細節。

但是,我們可以看幾個如何為 Hooks 提供類型的範例。

useState

useState Hook 會重複使用作為初始狀態傳入的值來決定值的類型應該為何。例如:

// Infer the type as "boolean"
const [enabled, setEnabled] = useState(false);

這會將 boolean 的類型指派給 enabled,而 setEnabled 將是一個接受 boolean 參數或返回 boolean 的函式的函式。如果您想明確地提供狀態的類型,您可以透過提供類型參數給 useState 呼叫來做到這一點。

// Explicitly set the type to "boolean"
const [enabled, setEnabled] = useState<boolean>(false);

在這種情況下,這並不是很有用,但您可能想要提供類型的常見情況是當您有一個聯集類型時。例如,這裡的 status 可以是幾個不同字串之一。

type Status = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<Status>("idle");

或者,如 狀態結構原則 中所建議的,您可以將相關狀態分組為一個物件,並透過物件類型描述不同的可能性。

type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success', data: any }
| { status: 'error', error: Error };

const [requestState, setRequestState] = useState<RequestState>({ status: 'idle' });

useReducer

useReducer Hook 是一個更複雜的 Hook,它接受一個 reducer 函式和一個初始狀態。reducer 函式的類型是從初始狀態推斷出來的。您可以選擇性地提供類型參數給 useReducer 呼叫來提供狀態的類型,但通常最好在初始狀態上設定類型。

import {useReducer} from 'react';

interface State {
   count: number 
};

type CounterAction =
  | { type: "reset" }
  | { type: "setCount"; value: State["count"] }

const initialState: State = { count: 0 };

function stateReducer(state: State, action: CounterAction): State {
  switch (action.type) {
    case "reset":
      return initialState;
    case "setCount":
      return { ...state, count: action.value };
    default:
      throw new Error("Unknown action");
  }
}

export default function App() {
  const [state, dispatch] = useReducer(stateReducer, initialState);

  const addFive = () => dispatch({ type: "setCount", value: state.count + 5 });
  const reset = () => dispatch({ type: "reset" });

  return (
    <div>
      <h1>Welcome to my counter</h1>

      <p>Count: {state.count}</p>
      <button onClick={addFive}>Add 5</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

我們在幾個關鍵地方使用 TypeScript:

  • interface State 描述 reducer 狀態的形狀。
  • type CounterAction 描述可以分派給 reducer 的不同動作。
  • const initialState: State 為初始狀態提供類型,也是 useReducer 預設使用的類型。
  • stateReducer(state: State, action: CounterAction): State 設定 reducer 函式參數和傳回值的類型。

除了在 initialState 上設定類型之外,更明確的替代方法是將類型參數提供給 useReducer

import { stateReducer, State } from './your-reducer-implementation';

const initialState = { count: 0 };

export default function App() {
const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}

useContext

useContext Hook 是一個用於將資料向下傳遞給組件樹的技术,无需透過 props 在組件之間傳遞。它的使用方法是創建一個提供者組件,並且通常會創建一個 Hook 以在子組件中使用該值。

上下文所提供的值的類型是從傳遞給 createContext 呼叫的值推斷出來的。

......
import { createContext, useContext, useState } from 'react';

type Theme = "light" | "dark" | "system";
const ThemeContext = createContext<Theme>("system");

const useGetTheme = () => useContext(ThemeContext);

export default function MyApp() {
  const [theme, setTheme] = useState<Theme>('light');

  return (
    <ThemeContext.Provider value={theme}>
      <MyComponent />
    </ThemeContext.Provider>
  )
}

function MyComponent() {
  const theme = useGetTheme();

  return (
    <div>
      <p>Current theme: {theme}</p>
    </div>
  )
}

當您有一個合理的預設值時,這種技術很有效 - 但有時您沒有預設值,在這些情況下,null 作為預設值似乎合理。但是,為了讓類型系統理解您的程式碼,您需要在 createContext 上明確設定 ContextShape | null

這會導致您需要在上下文使用者的類型中消除 | null 的問題。我們的建議是讓 Hook 進行執行時檢查,并在不存在時拋出錯誤。

import { createContext, useContext, useState, useMemo } from 'react';

// This is a simpler example, but you can imagine a more complex object here
type ComplexObject = {
kind: string
};

// The context is created with `| null` in the type, to accurately reflect the default value.
const Context = createContext<ComplexObject | null>(null);

// The `| null` will be removed via the check in the Hook.
const useGetComplexObject = () => {
const object = useContext(Context);
if (!object) { throw new Error("useGetComplexObject must be used within a Provider") }
return object;
}

export default function MyApp() {
const object = useMemo(() => ({ kind: "complex" }), []);

return (
<Context.Provider value={object}>
<MyComponent />
</Context.Provider>
)
}

function MyComponent() {
const object = useGetComplexObject();

return (
<div>
<p>Current object: {object.kind}</p>
</div>
)
}

useMemo ...

useMemo Hook 將從函數呼叫中創建/重新訪問一個記憶體值,僅當作為第二個參數傳遞的依賴項發生更改時才會重新運行該函數。呼叫 Hook 的結果是從第一個參數中函數的返回值推斷出來的。您可以透過向 Hook 提供類型參數來更明確地指定類型。

// The type of visibleTodos is inferred from the return value of filterTodos
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);

useCallback ...

useCallback 只要傳遞給第二個參數的依賴項相同,就會提供對函數的穩定參考。與 useMemo 一樣,函數的類型是從第一個參數中函數的返回值推斷出來的,您可以透過向 Hook 提供類型參數來更明確地指定類型。

const handleClick = useCallback(() => {
// ...
}, [todos]);

在 TypeScript 嚴格模式下工作時,useCallback 需要為回調函數中的參數添加類型。這是因為回調函數的類型是從函數的返回值推斷出來的,如果没有參數,則無法完全理解類型。

根據您的程式碼風格偏好,您可以使用 React 類型中的 *EventHandler 函數,在定義回調函數的同時提供事件處理程序的類型。

import { useState, useCallback } from 'react';

export default function Form() {
const [value, setValue] = useState("Change me");

const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
setValue(event.currentTarget.value);
}, [setValue])

return (
<>
<input value={value} onChange={handleChange} />
<p>Value: {value}</p>
</>
);
}

常用類型 ...

@types/react 套件提供了相當廣泛的類型,當您熟悉 React 和 TypeScript 的交互方式後,值得一讀。您可以在 DefinitelyTyped 中的 React 資料夾中找到它們。我們將在這裡介紹一些更常見的類型。

DOM 事件 ...

在 React 中使用 DOM 事件時,事件的類型通常可以從事件處理程序推斷出來。但是,當您想要提取一個函數以傳遞給事件處理程序時,您需要明確設定事件的類型。

import { useState } from 'react';

export default function Form() {
  const [value, setValue] = useState("Change me");

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setValue(event.currentTarget.value);
  }

  return (
    <>
      <input value={value} onChange={handleChange} />
      <p>Value: {value}</p>
    </>
  );
}

React 類型中提供了許多事件類型 - 完整列表可以在這裡找到,它基於DOM 中最常用的事件

在確定您要尋找的類型時,您可以先查看您正在使用的事件處理程式的懸停資訊,它會顯示事件的類型。

如果您需要使用此列表中未包含的事件,您可以使用 React.SyntheticEvent 類型,它是所有事件的基底類型。

子元素

描述組件的子元素有兩種常見途徑。第一種是使用 React.ReactNode 類型,它是所有可能在 JSX 中作為子元素傳遞的類型的聯合。

interface ModalRendererProps {
title: string;
children: React.ReactNode;
}

這是對子元素非常廣泛的定義。第二種是使用 React.ReactElement 類型,它僅適用於 JSX 元素,而不適用於 JavaScript 基礎類型,例如字串或數字。

interface ModalRendererProps {
title: string;
children: React.ReactElement;
}

請注意,您無法使用 TypeScript 描述子元素是某種類型的 JSX 元素,因此您無法使用類型系統來描述僅接受 <li> 子元素的組件。

您可以在這個 TypeScript playground 中看到 React.ReactNodeReact.ReactElement 與類型檢查器的範例。

樣式屬性

在 React 中使用內聯樣式時,您可以使用 React.CSSProperties 來描述傳遞給 style 屬性的物件。此類型是所有可能 CSS 屬性的聯合,是確保您將有效的 CSS 屬性傳遞給 style 屬性並在編輯器中獲得自動完成功能的好方法。

interface MyComponentProps {
style: React.CSSProperties;
}

進一步學習

本指南涵蓋了在 React 中使用 TypeScript 的基礎知識,但還有更多內容需要學習。文件中的個別 API 頁面可能包含更深入的說明,說明如何將它們與 TypeScript 一起使用。

我們推薦以下資源