Skip to content

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 类型:number
  • setCurrent 类型: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) => void

3. useMemo ​

1. 类型推断 ​

类型根据第一个参数中函数的返回值进行推断:

ts
const str = useMemo(() => 1 + "2", []); // str 类型被推断为 string

2. 传入泛型 ​

显式指定返回值的类型:

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 ​

两处类型定义:

  1. 使用 typeof 推断初始值 state 类型
  2. 使用联合类型定义 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