TypeScript 是一种流行的方法,用于向 JavaScript 代码库添加类型定义。TypeScript 开箱即用地支持 JSX,你可以通过添加 @types/react
和 @types/react-dom
到你的项目中来获得完整的 React Web 支持。
安装
所有 生产级的 React 框架 都提供对 TypeScript 的支持。请遵循框架特定的指南进行安装。
将 TypeScript 添加到现有的 React 项目
安装最新版本的 React 类型定义
需要在你的 tsconfig.json
中设置以下编译器选项
dom
必须包含在lib
中(注意:如果未指定lib
选项,则默认包含dom
)。jsx
必须设置为有效选项之一。preserve
应该足以满足大多数应用程序的需求。如果你正在发布库,请查阅jsx 文档
以了解应选择哪个值。
React 组件中的 TypeScript
使用 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> ); }
这种内联语法是为组件提供类型的最简单方法,但是一旦您开始需要描述几个字段,它就会变得笨拙。相反,您可以使用interface
或type
来描述组件的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的类型可以像您需要的那样简单或复杂,但它们应该是一个使用type
或interface
描述的对象类型。您可以在对象类型中了解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.ReactNode
和React.ReactElement
的示例。
样式属性
在React中使用内联样式时,可以使用React.CSSProperties
来描述传递给style
属性的对象。此类型是所有可能的CSS属性的联合,是确保您将有效的CSS属性传递给style
属性并获得编辑器自动完成功能的好方法。
interface MyComponentProps {
style: React.CSSProperties;
}
进一步学习
本指南涵盖了使用TypeScript与React的基本知识,但还有很多内容需要学习。文档中的各个API页面可能包含更深入的关于如何与TypeScript一起使用它们的文档。
我们推荐以下资源:
-
TypeScript 手册 是TypeScript的官方文档,涵盖了大多数关键的语言特性。
-
TypeScript 发布说明 深入介绍了新特性。
-
React TypeScript 速查表 是一个社区维护的速查表,用于将TypeScript与React一起使用,涵盖了许多有用的边缘情况,并提供了比本文档更广泛的内容。
-
TypeScript 社区 Discord 是一个提问并获得关于 TypeScript 和 React 问题的帮助的好地方。