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

使用 React 编写 TypeScript 与使用 React 编写 JavaScript 非常相似。使用组件时的主要区别在于,你可以为组件的 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;
}

进一步学习

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

我们推荐以下资源: