TypeScript 是一種流行的方式,可以將類型定義添加到 JavaScript 程式碼庫。TypeScript 原生支援 JSX,您可以透過將 @types/react
和 @types/react-dom
新增到您的專案中,以獲得完整的 React Web 支援。
安裝
所有 正式環境等級的 React 框架 都支援使用 TypeScript。請遵循框架特定的安裝指南
將 TypeScript 加入現有 React 專案
要安裝最新版本的 React 類型定義
需要在您的 tsconfig.json
中設定以下編譯器選項
dom
必須包含在lib
中(注意:如果沒有指定lib
選項,則預設包含dom
)。jsx
必須設定為其中一個有效選項。preserve
應該足以應付大多數應用程式。如果您要發佈程式庫,請參考jsx
文件 以瞭解要選擇哪個值。
React 元件搭配 TypeScript
使用 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> ); }
這種內聯語法是為元件提供類型的最簡單方法,但是一旦您開始要描述幾個欄位,它就會變得難以處理。您可以改用 interface
或 type
來描述元件的 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 的類型可以根據您的需要簡單或複雜,但它們應該是以 type
或 interface
描述的物件類型。您可以在物件類型中瞭解 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.ReactNode
和 React.ReactElement
與類型檢查器的範例。
樣式屬性
在 React 中使用內聯樣式時,您可以使用 React.CSSProperties
來描述傳遞給 style
屬性的物件。此類型是所有可能 CSS 屬性的聯合,是確保您將有效的 CSS 屬性傳遞給 style
屬性並在編輯器中獲得自動完成功能的好方法。
interface MyComponentProps {
style: React.CSSProperties;
}
進一步學習
本指南涵蓋了在 React 中使用 TypeScript 的基礎知識,但還有更多內容需要學習。文件中的個別 API 頁面可能包含更深入的說明,說明如何將它們與 TypeScript 一起使用。
我們推薦以下資源
-
TypeScript 手冊是 TypeScript 的官方文件,涵蓋了大多數主要的語言功能。
-
TypeScript 發行說明深入介紹了新功能。
-
React TypeScript 小抄是由社群維護的 React 與 TypeScript 使用小抄,涵蓋了許多有用的邊緣案例,並提供了比本文檔更廣泛的內容。.
-
TypeScript 社群 Discord 是一個提問和獲得 TypeScript 和 React 問題協助的好地方。