使用 TypeScript

TypeScript 是向 JavaScript 代码库添加类型定义的一种流行方式。开箱即用,TypeScript 支持 JSX,并且你可以通过将 @types/react@types/react-dom 添加到你的项目中来获得完整的 React Web 支持。

🌐 TypeScript is a popular way to add type definitions to JavaScript codebases. Out of the box, TypeScript supports JSX and you can get full React Web support by adding @types/react and @types/react-dom to your project.

安装

🌐 Installation

所有生产级 React 框架都支持使用 TypeScript。请按照框架特定的指南进行安装:

🌐 All production-grade React frameworks offer support for using TypeScript. Follow the framework specific guide for installation:

将 TypeScript 添加到现有的 React 项目

🌐 Adding TypeScript to an existing React project

要安装最新版本的 React 类型定义:

🌐 To install the latest version of React’s type definitions:

Terminal
npm install --save-dev @types/react @types/react-dom

需要在你的 tsconfig.json 中设置以下编译器选项:

🌐 The following compiler options need to be set in your tsconfig.json:

  1. dom 必须包含在 lib 中(注意:如果未指定 lib 选项,则默认包含 dom)。
  2. [jsx](https://ts.nodejs.cn/tsconfig/#jsx) 必须设置为有效选项之一。preserve 对大多数应用应该足够。如果你正在发布库,请查阅 jsx 文档 以了解选择何种值。

使用 React 组件的 TypeScript

🌐 TypeScript with React Components

注意

每个包含 JSX 的文件必须使用 .tsx 文件扩展名。这是 TypeScript 特定的扩展名,告诉 TypeScript 该文件包含 JSX。

🌐 Every file containing JSX must use the .tsx file extension. This is a TypeScript-specific extension that tells TypeScript that this file contains JSX.

用 TypeScript 编写 React 与用 JavaScript 编写 React 非常相似。在处理组件时,关键的区别在于你可以为组件的 props 提供类型。这些类型可以用于正确性检查,并在编辑器中提供内联文档。

🌐 Writing TypeScript with React is very similar to writing JavaScript with React. The key difference when working with a component is that you can provide types for your component’s props. These types can be used for correctness checking and providing inline documentation in editors.

快速入门指南中获取MyButton组件,我们可以为按钮添加描述 title 的类型:

🌐 Taking the MyButton component from the Quick Start guide, we can add a type describing the title for the button:

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

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="我是一个按钮" />
    </div>
  );
}

注意

这些沙箱可以处理 TypeScript 代码,但它们不会运行类型检查器。这意味着你可以修改 TypeScript 沙箱来学习,但你不会得到任何类型错误或警告。要进行类型检查,你可以使用 TypeScript Playground 或使用功能更完善的在线沙箱。

🌐 These sandboxes can handle TypeScript code, but they do not run the type-checker. This means you can amend the TypeScript sandboxes to learn, but you won’t get any type errors or warnings. To get type-checking, you can use the TypeScript Playground or use a more fully-featured online sandbox.

这种内联语法是为组件提供类型的最简单方式,尽管一旦你开始有几个字段需要描述,它可能会变得笨重。相反,你可以使用 interfacetype 来描述组件的 props:

🌐 This inline syntax is the simplest way to provide types for a component, though once you start to have a few fields to describe it can become unwieldy. Instead, you can use an interface or type to describe the component’s 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="我是一个禁用按钮" disabled={true}/>
    </div>
  );
}

描述组件 props 的类型可以根据需要简单或复杂,但它们应该是使用 typeinterface 描述的对象类型。你可以在 对象类型 中了解 TypeScript 如何描述对象,但你可能也会对使用 联合类型 来描述可以是几种不同类型之一的 prop 感兴趣,以及在 从类型创建类型 指南中了解更高级的用法。

🌐 The type describing your component’s props can be as simple or as complex as you need, though they should be an object type described with either a type or interface. You can learn about how TypeScript describes objects in Object Types but you may also be interested in using Union Types to describe a prop that can be one of a few different types and the Creating Types from Types guide for more advanced use cases.

钩子示例

🌐 Example Hooks

@types/react 的类型定义包含内置 Hooks 的类型,因此你可以在组件中使用它们而无需任何额外设置。它们的设计考虑了你在组件中编写的代码,因此你大部分时间会得到推断类型,理想情况下无需处理提供类型的细节。

🌐 The type definitions from @types/react include types for the built-in Hooks, so you can use them in your components without any additional setup. They are built to take into account the code you write in your component, so you will get inferred types a lot of the time and ideally do not need to handle the minutiae of providing the types.

不过,我们可以看一些如何为钩子提供类型的示例。

🌐 However, we can look at a few examples of how to provide types for Hooks.

useState

useState Hook 将重新使用作为初始状态传入的值来确定该值的类型。例如:

🌐 The useState Hook will re-use the value passed in as the initial state to determine what the type of the value should be. For example:

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

这将把 boolean 的类型分配给 enabled,而 setEnabled 将是一个函数,接受一个 boolean 参数,或者一个返回 boolean 的函数。如果你想为状态显式提供类型,可以通过向 useState 调用提供类型参数来实现:

🌐 This will assign the type of boolean to enabled, and setEnabled will be a function accepting either a boolean argument, or a function that returns a boolean. If you want to explicitly provide a type for the state, you can do so by providing a type argument to the useState call:

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

在这种情况下这不是很有用,但一个你可能想要提供类型的常见情况是当你有一个联合类型时。例如,这里的 status 可以是几种不同字符串之一:

🌐 This isn’t very useful in this case, but a common case where you may want to provide a type is when you have a union type. For example, status here can be one of a few different strings:

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

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

或者,如 状态结构原则 中建议的那样,你可以将相关状态归组为一个对象,并通过对象类型描述不同的可能性:

🌐 Or, as recommended in Principles for structuring state, you can group related state as an object and describe the different possibilities via object types:

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 调用提供一个类型参数来指定状态的类型,但通常更好的是直接在初始状态上设置类型:

🌐 The useReducer Hook is a more complex Hook that takes a reducer function and an initial state. The types for the reducer function are inferred from the initial state. You can optionally provide a type argument to the useReducer call to provide a type for the state, but it is often better to set the type on the initial state instead:

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:

🌐 We are using TypeScript in a few key places:

  • interface State 描述了 reducer 状态的形状。
  • type CounterAction 描述了可以分派给 reducer 的不同操作。
  • const initialState: State 提供了初始状态的类型,同时也是 useReducer 默认使用的类型。
  • stateReducer(state: State, action: CounterAction): State 为 reducer 函数的参数和返回值设置类型。

比在 initialState 上设置类型更明确的替代方法是为 useReducer 提供一个类型参数:

🌐 A more explicit alternative to setting the type on initialState is to provide a type argument to 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 在子组件中使用该值。

🌐 The useContext Hook is a technique for passing data down the component tree without having to pass props through components. It is used by creating a provider component and often by creating a Hook to consume the value in a child component.

上下文提供的值的类型是根据传递给 createContext 调用的值推断的:

🌐 The type of the value provided by the context is inferred from the value passed to the createContext call:

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 value={theme}>
      <MyComponent />
    </ThemeContext>
  )
}

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

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

当你有一个合理的默认值时,这种技术是有效的——但有时你没有默认值,在这些情况下,将 null 作为默认值可能显得合理。然而,为了让类型系统理解你的代码,你需要在 createContext 上显式设置 ContextShape | null

🌐 This technique works when you have a default value which makes sense - but there are occasionally cases when you do not, and in those cases null can feel reasonable as a default value. However, to allow the type-system to understand your code, you need to explicitly set ContextShape | null on the createContext.

这会导致需要在上下文使用者的类型中消除 | null 的问题。我们的建议是让 Hook 在运行时检查其是否存在,并在不存在时抛出错误:

🌐 This causes the issue that you need to eliminate the | null in the type for context consumers. Our recommendation is to have the Hook do a runtime check for it’s existence and throw an error when not present:

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 value={object}>
<MyComponent />
</Context>
)
}

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

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

useMemo

注意

React 编译器 会自动缓存值和函数,从而减少手动 useMemo 调用的需求。你可以使用编译器自动处理缓存。

useMemo Hooks 将从函数调用中创建/重新访问一个记忆值,只有当作为第二个参数传递的依赖发生变化时才重新运行该函数。调用 Hook 的结果是根据第一个参数中函数的返回值推断的。你可以通过向 Hook 提供类型参数来更加明确。

🌐 The useMemo Hooks will create/re-access a memorized value from a function call, re-running the function only when dependencies passed as the 2nd parameter are changed. The result of calling the Hook is inferred from the return value from the function in the first parameter. You can be more explicit by providing a type argument to the Hook.

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

useCallback

注意

React 编译器 会自动缓存值和函数,从而减少手动 useCallback 调用的需求。你可以使用编译器自动处理缓存。

useCallback 提供对一个函数的稳定引用,只要传入第二个参数的依赖保持不变。和 useMemo 一样,该函数的类型会根据第一个参数中函数的返回值进行推断,你也可以通过为 Hook 提供类型参数来更明确地指定类型。

🌐 The useCallback provide a stable reference to a function as long as the dependencies passed into the second parameter are the same. Like useMemo, the function’s type is inferred from the return value of the function in the first parameter, and you can be more explicit by providing a type argument to the Hook.

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

在 TypeScript 的严格模式下,useCallback 需要为回调函数的参数添加类型。这是因为回调函数的类型是从函数的返回值推断的,如果没有参数,类型就无法被完全理解。

🌐 When working in TypeScript strict mode useCallback requires adding types for the parameters in your callback. This is because the type of the callback is inferred from the return value of the function, and without parameters the type cannot be fully understood.

根据你的代码风格偏好,你可以使用 React 类型中的 *EventHandler 函数在定义回调的同时为事件处理程序提供类型:

🌐 Depending on your code-style preferences, you could use the *EventHandler functions from the React types to provide the type for the event handler at the same time as defining the callback:

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>
</>
);
}

有用的类型

🌐 Useful Types

@types/react 包中有相当广泛的一组类型,当你对 React 和 TypeScript 的交互感觉熟悉时,值得阅读一遍。你可以在 DefinitelyTyped 中的 React 文件夹 找到它们。我们将在这里介绍一些更常见的类型。

🌐 There is quite an expansive set of types which come from the @types/react package, it is worth a read when you feel comfortable with how React and TypeScript interact. You can find them in React’s folder in DefinitelyTyped. We will cover a few of the more common types here.

DOM 事件

🌐 DOM Events

在使用 React 处理 DOM 事件时,事件的类型通常可以从事件处理程序中推断出来。然而,当你想提取一个函数以传递给事件处理程序时,你需要显式设置事件的类型。

🌐 When working with DOM events in React, the type of the event can often be inferred from the event handler. However, when you want to extract a function to be passed to an event handler, you will need to explicitly set the type of the event.

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 中最流行的事件

🌐 There are many types of events provided in the React types - the full list can be found here which is based on the most popular events from the DOM.

在确定你要查找的类型时,你可以首先查看你正在使用的事件处理程序的悬停信息,其中将显示事件的类型。

🌐 When determining the type you are looking for you can first look at the hover information for the event handler you are using, which will show the type of the event.

如果你需要使用不在此列表中的事件,你可以使用 React.SyntheticEvent 类型,它是所有事件的基类型。

🌐 If you need to use an event that is not included in this list, you can use the React.SyntheticEvent type, which is the base type for all events.

Children

描述组件的子元素有两条常见路径。第一种是使用 React.ReactNode 类型,它是所有可以作为 JSX 子元素传递的可能类型的联合类型:

🌐 There are two common paths to describing the children of a component. The first is to use the React.ReactNode type, which is a union of all the possible types that can be passed as children in JSX:

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

这是一个对 children 的非常广泛的定义。第二种是使用 React.ReactElement 类型,它只包括 JSX 元素,而不包括像字符串或数字这样的 JavaScript 原始类型:

🌐 This is a very broad definition of children. The second is to use the React.ReactElement type, which is only JSX elements and not JavaScript primitives like strings or numbers:

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

注意,你不能使用 TypeScript 来描述子元素是某种类型的 JSX 元素,因此你不能使用类型系统来描述一个只接受 <li> 子元素的组件。

🌐 Note, that you cannot use TypeScript to describe that the children are a certain type of JSX elements, so you cannot use the type-system to describe a component which only accepts <li> children.

你可以在这个 TypeScript Playground中看到 React.ReactNodeReact.ReactElement 的类型检查器示例。

🌐 You can see an example of both React.ReactNode and React.ReactElement with the type-checker in this TypeScript playground.

样式属性

🌐 Style Props

在 React 中使用内联样式时,你可以使用 React.CSSProperties 来描述传递给 style 属性的对象。这个类型是所有可能的 CSS 属性的联合类型,是确保你向 style 属性传递有效 CSS 属性并在编辑器中获得自动补全的好方法。

🌐 When using inline styles in React, you can use React.CSSProperties to describe the object passed to the style prop. This type is a union of all the possible CSS properties, and is a good way to ensure you are passing valid CSS properties to the style prop, and to get auto-complete in your editor.

interface MyComponentProps {
style: React.CSSProperties;
}

进一步学习

🌐 Further learning

本指南已经涵盖了在 React 中使用 TypeScript 的基础知识,但还有很多内容需要学习。文档中的各个 API 页面可能包含有关如何在 TypeScript 中使用它们的更深入的文档。

🌐 This guide has covered the basics of using TypeScript with React, but there is a lot more to learn. Individual API pages on the docs may contain more in-depth documentation on how to use them with TypeScript.

我们推荐以下资源:

🌐 We recommend the following resources: