使用 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 游乐场 或使用功能更全面的在线沙盒。

这种内联语法是为组件提供类型最简单的方法,但一旦你开始拥有几个字段来描述它,它可能会变得难以处理。相反,你可以使用 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>
  );
}

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

示例 Hook

来自 @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 接受一个 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 是一种无需通过组件传递属性即可将数据向下传递到组件树中的技术。它通过创建提供程序组件来使用,通常通过创建 Hook 来使用子组件中的值。

上下文提供的 value 的类型从传递给 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 游乐场 中看到 React.ReactNodeReact.ReactElement 的示例。

样式属性

在 React 中使用内联样式时,可以使用 React.CSSProperties 来描述传递给 style 属性的对象。此类型是所有可能的 CSS 属性的联合,并且是确保将有效的 CSS 属性传递给 style 属性以及在编辑器中获取自动完成的不错方式。

interface MyComponentProps {
style: React.CSSProperties;
}

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

我们推荐以下资源