使用 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 @types/react @types/react-dom

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

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

  1. dom 必须包含在 lib 中(注意:如果未指定 lib 选项,则默认包含 dom)。

    ¥dom must be included in lib (Note: If no lib option is specified, dom is included by default).

  2. jsx 必须设置为有效选项之一。preserve 对于大多数应用来说应该足够了。如果你要发布库,请咨询 jsx 文档 以了解选择什么值。

    ¥jsx must be set to one of the valid options. preserve should suffice for most applications. If you’re publishing a library, consult the jsx documentation on what value to choose.

使用 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.

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

¥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="I'm a button" />
    </div>
  );
}

注意

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

¥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 来描述组件的属性:

¥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="I'm a disabled button" disabled={true}/>
    </div>
  );
}

描述组件属性的类型可以根据需要简单或复杂,但它们应该是用 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 中的类型定义包括内置钩子的类型,因此你可以在组件中使用它们,而无需任何额外的设置。它们的构建考虑了你在组件中编写的代码,因此你在很多时候都会得到 推断类型,并且理想情况下不需要处理提供类型的细节。

¥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 钩子 将重新使用传入的值作为初始状态来确定该值的类型应该是什么。例如:

¥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 的类型分配给 enabledsetEnabled 将是一个接受 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,它采用 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 状态的形状。

    ¥interface State describes the shape of the reducer’s state.

  • type CounterAction 描述了可以分派到 reducer 的不同操作。

    ¥type CounterAction describes the different actions which can be dispatched to the reducer.

  • const initialState: State 提供了初始状态的类型,也是 useReducer 默认使用的类型。

    ¥const initialState: State provides a type for the initial state, and also the type which is used by useReducer by default.

  • stateReducer(state: State, action: CounterAction): State 设置 reducer 函数的参数和返回值的类型。

    ¥stateReducer(state: State, action: CounterAction): State sets the types for the reducer function’s arguments and return value.

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 钩子 是一种在组件树中传递数据而无需通过组件传递属性的技术。它通过创建提供者组件来使用,并且通常通过创建钩子来使用子组件中的值来使用。

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

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 的问题。我们的建议是让钩子在运行时检查它是否存在,并在不存在时抛出错误:

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

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

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

useMemo

useMemo 钩子将创建/重新访问函数调用中存储的值,仅当作为第二个参数传递的依赖发生更改时才重新运行该函数。调用钩子的结果是根据第一个参数中函数的返回值推断出来的。你可以通过向钩子提供类型参数来更加明确。

¥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

只要传递到第二个参数的依赖相同,useCallback 就会提供对函数的稳定引用。与 useMemo 一样,函数的类型是根据第一个参数中函数的返回值推断的,并且你可以通过向钩子提供类型参数来更加明确。

¥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 的交互方式时,值得一读。你可以找到他们 在 React 的 DefinitelyTyped 文件夹中。我们将在这里介绍一些更常见的类型。

¥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;
}

这是对子级的非常广泛的定义。第二种是使用 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 在线运行 中使用类型检查器查看 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 属性的联合,是确保将有效的 CSS 属性传递给 style 属性并在编辑器中自动补齐的好方法。

¥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

本指南涵盖了将 TypeScript 与 React 结合使用的基础知识,但还有很多东西需要学习。文档上的各个 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:


React 中文网 - 粤ICP备13048890号