TypeScript 是一种向 JavaScript 代码添加类型定义的常用方法。TypeScript 天然支持 JSX——只需在项目中添加
@types/react
和
@types/react-dom
即可获得完整的 React Web 支持。
你将会学习到
- 在 React 组件中使用 TypeScript
- 带有 Hook 的类型示例
-
来自
@types/react的常见类型 - 更多学习资源
- Next.js
- Remix
- Gatsby
-
必须在
lib中包含dom(注意:如果没有指定lib选项,默认情况下会包含dom)。 -
jsx必须设置为一个有效的选项。对于大多数应用程序,preserve应该足够了。 如果你正在发布一个库,请查阅jsx文档 以选择合适的值。
所有的 生产级 React 框架 都支持使用 TypeScript。请按照框架特定的指南进行安装:
在现有 React 项目中添加 TypeScript
使用下面命令安装最新版本的 React 类型定义:
然后在
tsconfig.json
中设置以下编译器选项:
在 React 组件中使用 TypeScript
使用 TypeScript 编写 React 与使用 JavaScript 编写 React 非常相似。与组件一起工作时的关键区别是,你可以为组件的 props 提供类型。这些类型可用于正确性检查,并在编辑器中提供内联文档。
以
快速入门
指南中的
MyButton
组件
为例,我们可以为按钮的
title
添加一个描述类型:
function MyButton({ title }: { title: string }) { return ( <button>{title}</button> export default function MyApp() { return ( <h1>欢迎来到我的应用</h1> <MyButton title="我是一个按钮" /> </div>这种内联语法是为组件提供类型的最简单方法,但是一旦你开始描述几个字段,它可能变得难以管理。相反,你可以使用
interface或type来描述组件的 props:
interface MyButtonProps { /** 按钮文字 */ title: string; /** 按钮是否禁用 */ disabled: boolean; function MyButton({ title, disabled }: MyButtonProps) { return ( <button disabled={disabled}>{title}</button> export default function MyApp() { return ( <h1>Welcome to my app</h1> <MyButton title="我是一个禁用按钮" disabled={true}/> </div>描述组件 props 的类型可以根据需要变得简单或复杂,但它们应该是使用
type或interface描述的对象类型。你可以在 对象类型 中了解 TypeScript 如何描述对象,但你可能还对使用 联合类型 描述可以是几种不同类型之一的 prop,以及在 从类型创建类型 指南中参考更高级的用例。Hooks 示例
来自
@types/react的类型定义包括内置的 Hook,因此你可以在组件中使用它们,无需任何额外设置。它们是根据你在组件中编写的代码构建的,所以你会得到很多 类型推断,并且理想情况下不需要处理提供类型的细节。但是,我们可以看一下如何为 Hook 提供类型的几个示例。
useState
useStateHook 会重用作为初始 state 传入的值以确定值的类型。例如:// 推断类型为 "boolean"const [enabled, setEnabled] = useState(false);这将为
enabled分配boolean类型,而setEnabled将是一个接受boolean参数的函数,或者返回boolean的函数。如果你想为 state 显式提供一个类型,你可以通过为useState调用提供一个类型参数来实现:// 显式设置类型为 "boolean"const [enabled, setEnabled] = useState<boolean>(false);在这种情况下,这并不是很有用,但是当你有一个联合类型时,你可能想要提供一个
type。例如,这里的status可以是几个不同的字符串之一:type Status = "idle" | "loading" | "success" | "error";const [status, setStatus] = useState<Status>("idle");或者,如 选择 state 结构原则 中推荐的,你可以将相关的 state 作为一个对象分组,并通过对象类型描述不同的可能性:
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 函数和一个初始 state 作为参数,并将从初始 state 推断出 reducer 函数的类型。你可以选择性地为useReducer提供类型参数以为 state 提供类型。但是更好的做法仍然是在初始 state 上添加类型:
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 ( <h1>欢迎来到我的计数器</h1> <p>计数: {state.count}</p> <button onClick={addFive}>加 5</button> <button onClick={reset}>重置</button> </div>我们在几个关键位置使用了 TypeScript:
interface State 描述了 reducer state 的类型。type CounterAction 描述了可以 dispatch 至 reducer 的不同 action。const initialState: State 为初始 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 是一种无需通过组件传递 props 而可以直接在组件树中传递数据的技术。它是通过创建 provider 组件使用,通常还会创建一个 Hook 以在子组件中使用该值。
从传递给 createContext 调用的值推断 context 提供的值的类型:
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 ( <p>当前主题:{theme}</p> </div>当你有一个合理的默认值情况下,这种技术是有效的。但是当你没有合理的默认值的时候,
null作为默认值可能感觉是合理的。但是为了让类型系统理解你的代码,你需要在createContext上显式设置ContextShape | null。这会导致一个问题,你需要在 context consumer 中消除
| null的类型。我们建议让 Hook 在运行时检查它的存在,并在不存在时抛出一个错误:import { createContext, useContext, useState, useMemo } from 'react';// 这是一个简单的示例,但你可以想象一个更复杂的对象type ComplexObject = {kind: string};// 上下文在类型中创建为 `| null`,以准确反映默认值。const Context = createContext<ComplexObject | null>(null);// 这个 Hook 会在运行时检查 context 是否存在,并在不存在时抛出一个错误。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
useMemo会从函数调用中创建/重新访问记忆化值,只有在第二个参数中传入的依赖项发生变化时,才会重新运行该函数。函数的类型是根据第一个参数中函数的返回值进行推断的,如果希望明确指定,可以为该 Hook 提供一个类型参数以指定函数类型。// 从 filterTodos 的返回值推断 visibleTodos 的类型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}</p></>);
}当逐渐适应 React 和 TypeScript 的搭配使用后, 可以尝试阅读
@types/react,此库提供了一整套类型。你可以在 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}</p>React 类型中提供了许多事件类型 —— 完整列表可以在 这里 查看,它基于 DOM 的常用事件。
当你需要确定某个类型时,可以先将鼠标悬停在你使用的事件处理器上,这样可以查看到事件的具体类型。
当你需要使用不包含在此列表中的事件时,你可以使用
React.SyntheticEvent类型,这是所有事件的基类型。描述组件的子元素有两种常见方法。第一种是使用
React.ReactNode类型,这是可以在 JSX 中作为子元素传递的所有可能类型的并集:interface ModalRendererProps {title: string;children: React.ReactNode;}这是对子元素的一个非常宽泛的定义。第二种方法是使用
React.ReactElement类型,它只包括 JSX 元素,而不包括 JavaScript 原始类型,如 string 或 number:interface ModalRendererProps {title: string;children: React.ReactElement;}注意,你不能使用 TypeScript 来描述子元素是某种类型的 JSX 元素,所以你不能使用类型系统来描述一个只接受
<li>子元素的组件。你可以在这个 TypeScript playground 中查看
React.ReactNode和React.ReactElement的示例,并使用类型检查器进行验证。当在 React 中使用内联样式时,你可以使用
React.CSSProperties来描述传递给style属性的对象。这个类型是所有可能的 CSS 属性的并集,它能确保你传递给style属性的是有效的 CSS 属性,并且你能在编辑器中获得样式编码提示。interface MyComponentProps {style: React.CSSProperties;}更多学习资源
本指南已经介绍了如何在 React 中使用 TypeScript 的基础知识,但还有更多内容等待学习。官网中的单个 API 页面或许包含了如何与 TypeScript 一起使用它们的更深入的说明。 文档中的各个 API 页面可能会包含更深入的说明,介绍如何在 TypeScript 中使用它们。
我们推荐以下资源:
TypeScript 官方文档 涵盖了大多数关键的语言特性。
TypeScript 发布日志 深入介绍了每一个新特性。