主题
React 18 + TypeScript 类型笔记
汇总函数组件的 props 声明、Hooks 的类型推断、React 内置类型,以及事件与事件处理函数的类型写法。
一、函数组件
1. 外部声明
使用 interface 或 type 单独声明 props 类型,再在组件签名中引用:
tsx
// 使用 interface 或 type
interface AppProps {
message: string;
}
const App = ({ message }: AppProps) => <div>{message}</div>;2. 内联声明
tsx
const App = ({ message }: { message: string }) => <div>{message}</div>;3. 使用泛型
定义:
tsx
// StatisticsLayout.tsx
export interface StatisticsLayoutProps<T> {
/* ... */
}
function StatisticsLayout<T>(props: StatisticsLayoutProps<T>) {
// ... 实现
}
// 如果需要 memo,用 as 断言
export default React.memo(StatisticsLayout) as <T>(
props: StatisticsLayoutProps<T>,
) => JSX.Element;使用:
tsx
import StatisticsLayout from './StatisticsLayout'
<StatisticsLayout<RiskTrendMainRow>
loading={false}
mainColumns={...}
mainDataSource={...}
pager={{ pageSize: 10, current: 1, total: 0 }}
tabConf={[...]}
tabBarExtra={() => null}
selectedMainRow={...}
onMainRowClick={() => {}}
/>二、Hooks
1. useState
1. 类型推断
指定初始值后无需手动定义类型:
ts
const [current, setCurrent] = useState(1);current类型:numbersetCurrent类型:Dispatch<React.SetStateAction<number>>
2. 传入泛型
显式指定 state 的类型:
tsx
interface User {
userName: string;
userId: number;
}
function UserInfo() {
const [userInfo, setUserInfo] = useState<User | undefined>();
// userInfo 类型为 User 或 undefined
}3. 不指定初始值时的取值问题
当泛型包含 undefined 时(例如 useState<User | undefined>),对象取值时会遇到「对象可能未定义」的校验错误。
解决方法 1:使用可选链操作符 ?.
tsx
function UserInfo() {
const [userInfo, setUserInfo] = useState<User | undefined>();
return <div>{userInfo?.userName}</div>;
}解决方法 2:初始值给一个空对象,并使用类型断言
ts
const [userInfo, setUserInfo] = useState<User>({} as User);2. useCallback
函数的类型根据第一个参数进行推断:
ts
const memoizedCallback = useCallback(
(param1: string, param2: number) => {},
[],
);memoizedCallback 的类型被推断为:
ts
(param1: string, param2: number) => void3. useMemo
1. 类型推断
类型根据第一个参数中函数的返回值进行推断:
ts
const str = useMemo(() => 1 + "2", []); // str 类型被推断为 string2. 传入泛型
显式指定返回值的类型:
ts
const str = useMemo<number>(() => 1 + "2", []);
// 报错:不能将类型“string”分配给类型“number”4. useRef
1. 用来访问 DOM 节点
初始值给 null:
ts
const divRef = useRef<HTMLDivElement>(null);2. 用来清除定时器
ts
const intervalRef = useRef<NodeJS.Timer>();
useEffect(() => {
intervalRef.current = setInterval(() => {
console.log("打印");
}, 200);
return () => clearInterval(intervalRef.current);
}, []);5. useImperativeHandle
类型定义在 forwardRef 处,在 forwardRef 中指定 props 与 ref 的类型。
注意:泛型参数的传递顺序是 ref 在前、props 在后。
tsx
import { useRef, forwardRef, useImperativeHandle, useState } from "react";
// 定义 ref 类型
interface ForwardObject {
reset: () => void;
}
const App = () => {
// 给一个初始值 null
const countRef = useRef<ForwardObject>(null);
const handleClick = () => {
countRef.current?.reset();
};
return (
<div>
<Count ref={countRef} />
<button onClick={handleClick}>重置</button>
</div>
);
};
// 注意:props、ref 类型定义的顺序是反的
const Count = forwardRef<ForwardObject, unknown>((props, ref) => {
const [count, setCount] = useState(0);
useImperativeHandle(ref, () => ({
reset: handleReset,
}));
const handleReset = () => {
setCount(0);
};
return (
<div>
{count}
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
});
export default App;6. useContext
使用 useContext 时会自动推断出上下文对象的类型,因此类型主要定义在 createContext 处。
1. 类型推断
指定初始值后无需手动定义类型:
ts
const UserContext = createContext({
name: "张三",
age: 20,
});UserContext 推断为:
ts
const UserContext: React.Context<{
name: string;
age: number;
}>;2. 传入泛型
将空对象作为默认值,使用类型断言转换为预期的上下文类型:
ts
interface UserContextType {
name: string;
age: number;
}
const UserContext = createContext<UserContextType>({} as UserContextType);7. useReducer
两处类型定义:
- 使用
typeof推断初始值 state 类型 - 使用联合类型定义 reducer 的 action 类型
tsx
import { useReducer } from "react";
const initialCount = {
count: 0,
};
// 1. 定义初始值类型
type State = typeof initialCount;
// 2. 定义 action 类型
type ActionType = { type: "increment" } | { type: "decrement" };
function reducer(state: State, action: ActionType) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
const App = () => {
const [state, dispatch] = useReducer(reducer, initialCount);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
};
export default App;8. useEffect / useLayoutEffect
useEffect 与 useLayoutEffect 都用于执行副作用,不需要手动定义类型。
三、内置类型
1. 样式属性类型
在 React 中,当想给子组件传递一个 style 内联样式时,可使用 React.CSSProperties:
ts
interface Props {
style?: React.CSSProperties;
}2. 子元素类型
1. React.ReactNode
在 JSX 中作为子元素传递的所有可能类型的并集,可以是 string、number,也可以是 ReactElement、ReactNodeArray:
ts
interface Props {
children?: React.ReactNode;
}2. React.ReactElement
只包括 JSX 元素,不包括 string 或 number 这类 JavaScript 原始类型,常用来定义函数组件的返回值类型:
ts
interface Props {
children?: React.ReactElement;
}3. 获取组件 props 类型
如果子组件使用内联类型定义 props,而父组件需要子组件的 props 类型时,可使用 React.ComponentProps 推断:
tsx
const Children = ({ message }: { message: string }) => <div>{message}</div>;
type PropsType = React.ComponentProps<typeof Children>;PropsType 类型为:
ts
type PropsType = {
message: string;
};4. key 类型
ts
React.Key; // type Key = string | number四、事件处理
1. event 事件类型
1. onClick 事件
将 HTMLButtonElement 作为泛型参数传入 React.MouseEvent,表示 button 标签的鼠标事件:
ts
interface Props {
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
}2. onChange 事件
将 HTMLInputElement 作为泛型参数传入 React.ChangeEvent,表示 input 标签的表单域值变更事件:
tsx
function App() {
const [value, setValue] = useState("");
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
return <input value={value} onChange={handleChange} />;
}3. 常用 event 事件类型
| 事件类别 | 事件类型 |
|---|---|
| 鼠标事件 | React.MouseEvent |
| change 事件 | React.ChangeEvent |
| 键盘事件 | React.KeyboardEvent |
| 拖拽事件 | React.DragEvent |
| 焦点事件 | React.FocusEvent |
| 表单事件 | React.FormEvent |
| 鼠标滚动事件 | React.WheelEvent |
| 触摸事件 | React.TouchEvent |
| 剪贴板事件 | React.ClipboardEvent |
| 动画事件 | React.AnimationEvent |
| 过渡事件 | React.TransitionEvent |
4. HTML 标签与类型映射关系
常见的标签及其类型如下:
ts
interface HTMLElementTagNameMap {
a: HTMLAnchorElement;
abbr: HTMLElement;
address: HTMLElement;
area: HTMLAreaElement;
article: HTMLElement;
aside: HTMLElement;
audio: HTMLAudioElement;
b: HTMLElement;
base: HTMLBaseElement;
bdi: HTMLElement;
bdo: HTMLElement;
blockquote: HTMLQuoteElement;
body: HTMLBodyElement;
br: HTMLBRElement;
button: HTMLButtonElement;
canvas: HTMLCanvasElement;
caption: HTMLTableCaptionElement;
cite: HTMLElement;
code: HTMLElement;
col: HTMLTableColElement;
colgroup: HTMLTableColElement;
data: HTMLDataElement;
datalist: HTMLDataListElement;
dd: HTMLElement;
del: HTMLModElement;
details: HTMLDetailsElement;
dfn: HTMLElement;
dialog: HTMLDialogElement;
div: HTMLDivElement;
dl: HTMLDListElement;
dt: HTMLElement;
em: HTMLElement;
embed: HTMLEmbedElement;
fieldset: HTMLFieldSetElement;
figcaption: HTMLElement;
figure: HTMLElement;
footer: HTMLElement;
form: HTMLFormElement;
h1: HTMLHeadingElement;
h2: HTMLHeadingElement;
h3: HTMLHeadingElement;
h4: HTMLHeadingElement;
h5: HTMLHeadingElement;
h6: HTMLHeadingElement;
head: HTMLHeadElement;
header: HTMLElement;
hgroup: HTMLElement;
hr: HTMLHRElement;
html: HTMLHtmlElement;
i: HTMLElement;
iframe: HTMLIFrameElement;
img: HTMLImageElement;
input: HTMLInputElement;
ins: HTMLModElement;
kbd: HTMLElement;
label: HTMLLabelElement;
legend: HTMLLegendElement;
li: HTMLLIElement;
link: HTMLLinkElement;
main: HTMLElement;
map: HTMLMapElement;
mark: HTMLElement;
menu: HTMLMenuElement;
meta: HTMLMetaElement;
meter: HTMLMeterElement;
nav: HTMLElement;
noscript: HTMLElement;
object: HTMLObjectElement;
ol: HTMLOListElement;
optgroup: HTMLOptGroupElement;
option: HTMLOptionElement;
output: HTMLOutputElement;
p: HTMLParagraphElement;
picture: HTMLPictureElement;
pre: HTMLPreElement;
progress: HTMLProgressElement;
q: HTMLQuoteElement;
rp: HTMLElement;
rt: HTMLElement;
ruby: HTMLElement;
s: HTMLElement;
samp: HTMLElement;
script: HTMLScriptElement;
search: HTMLElement;
section: HTMLElement;
select: HTMLSelectElement;
slot: HTMLSlotElement;
small: HTMLElement;
source: HTMLSourceElement;
span: HTMLSpanElement;
strong: HTMLElement;
style: HTMLStyleElement;
sub: HTMLElement;
summary: HTMLElement;
sup: HTMLElement;
table: HTMLTableElement;
tbody: HTMLTableSectionElement;
td: HTMLTableCellElement;
template: HTMLTemplateElement;
textarea: HTMLTextAreaElement;
tfoot: HTMLTableSectionElement;
th: HTMLTableCellElement;
thead: HTMLTableSectionElement;
time: HTMLTimeElement;
title: HTMLTitleElement;
tr: HTMLTableRowElement;
track: HTMLTrackElement;
u: HTMLElement;
ul: HTMLUListElement;
var: HTMLElement;
video: HTMLVideoElement;
wbr: HTMLElement;
}2. 事件处理函数类型
1. onChange 事件
除了使用 Event 事件类型,还可以使用 React 提供的事件处理函数类型,例如上面 input 的 change 事件:
ts
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};可以写成:
ts
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
setValue(event.target.value);
};此时 event 事件对象被自动推断为 React.ChangeEvent<HTMLInputElement> 类型。
2. 常用事件处理函数类型
| 事件类别 | 事件处理函数类型 |
|---|---|
| 鼠标事件 | React.MouseEventHandler |
| change 事件 | React.ChangeEventHandler |
| 键盘事件 | React.KeyboardEventHandler |
| 拖拽事件 | React.DragEventHandler |
| 焦点事件 | React.FocusEventHandler |
| 表单事件 | React.FormEventHandler |
| 鼠标滚动事件 | React.WheelEventHandler |
| 触摸事件 | React.TouchEventHandler |
| 剪贴板事件 | React.ClipboardEventHandler |
| 动画事件 | React.AnimationEventHandler |
| 过渡事件 | React.TransitionEventHandler |