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> ); }
描述组件属性的类型可以根据需要简单或复杂,但它们应为使用 type
或 interface
描述的对象类型。您可以在 对象类型 中了解 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.ReactNode
和 React.ReactElement
的示例。
样式属性
在 React 中使用内联样式时,可以使用 React.CSSProperties
来描述传递给 style
属性的对象。此类型是所有可能的 CSS 属性的联合,并且是确保将有效的 CSS 属性传递给 style
属性以及在编辑器中获取自动完成的不错方式。
interface MyComponentProps {
style: React.CSSProperties;
}