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:
需要在 tsconfig.json
中设置以下编译器选项:
¥The following compiler options need to be set in your tsconfig.json
:
-
dom
必须包含在lib
中(注意:如果未指定lib
选项,则默认包含dom
)。¥
dom
must be included inlib
(Note: If nolib
option is specified,dom
is included by default). -
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 thejsx
documentation on what value to choose.
使用 React 组件的 TypeScript
¥TypeScript with React Components
使用 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> ); }
这种内联语法是为组件提供类型的最简单方法,尽管一旦开始使用一些字段来描述它可能会变得笨拙。相反,你可以使用 interface
或 type
来描述组件的属性:
¥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> ); }
描述组件属性的类型可以根据需要简单或复杂,但它们应该是用 type
或 interface
描述的对象类型。你可以了解 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
的类型分配给 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,它采用 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 byuseReducer
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.ReactNode
和 React.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:
-
TypeScript 手册 是 TypeScript 的官方文档,涵盖了大多数关键的语言功能。
¥The TypeScript handbook is the official documentation for TypeScript, and covers most key language features.
-
TypeScript 发行说明 深入介绍新功能。
¥The TypeScript release notes cover new features in depth.
-
React TypeScript 备忘单 是一个社区维护的备忘单,用于将 TypeScript 与 React 结合使用,涵盖了许多有用的边缘情况,并提供了比本文档更广泛的内容。
¥React TypeScript Cheatsheet is a community-maintained cheatsheet for using TypeScript with React, covering a lot of useful edge cases and providing more breadth than this document.
-
TypeScript 社区 Discord 是提出问题并获得 TypeScript 和 React 问题帮助的好地方。
¥TypeScript Community Discord is a great place to ask questions and get help with TypeScript and React issues.