react学习8:React 组件如何写 TypeScript 类型

0 阅读3分钟

JSX 的类型

在 App.tsx 里开始练习 TypeScript 类型:

interface AaaProps {
  name: string;
}

function Aaa(props: AaaProps) {
  return <div>aaa, {props.name}</div>
}

function App() {
  return <div>
    <Aaa name="guang"></Aaa>
  </div>
}

export default App;

其实组件我们一般不写返回值类型,就用默认推导出来的。

image.png

React 函数组件默认返回值就是 JSX.Element。

我们看下 JSX.Element 的类型定义:

const content: JSX.Element = <div>aaa</div>

image.png

可以看到它就是 React.ReactElement。

也就是说,如果你想描述一个 jsx 类型,就用 React.ReactElement 就好了。

比如 Aaa 组件有一个 content 的 props,类型为 ReactElement:

image.png

这样就只能传入 JSX。

ReactElement 就是 jsx 类型,但如果你传入 null、number 等就报错了:

image.png

那如果有的时候就是 number、null 呢?

换成 ReactNode 就好了:

image.png

看下它的类型定义:

image.png

ReactNode 包含 ReactElement、或者 number、string、null、boolean 等可以写在 JSX 里的类型。

这三个类型的关系 ReactNode > ReactElement > JSX.Element。

所以,一般情况下,如果你想描述一个参数接收 JSX 类型,就用 ReactNode 就行。

函数组件的类型

前面的函数组件,我们都没明确定义类型:

image.png

其实它的类型是 FunctionComponent:

image.png

看下它的类型定义:

image.png

可以看到,FC 和 FunctionComponent 等价,参数是 Props,返回值是 ReactNode。

hook 的类型

useState

一般用推导出的类型就行:

image.png

也可以手动声明类型:

image.png

useRef

useRef 我们知道,可以保存 dom 引用或者其他内容。

所以它的类型也有两种。

保存 dom 引用的时候,参数需要传个 null:

image.png

不然会报错:

image.png

而保存别的内容的时候,不能传 null,不然也会报错,说是 current 只读,这很合理,因为保存的 dom 引用肯定不能改呀。

image.png

因为 ref 既可以保存 dom 引用,又可以保存其他数据,而保存 dom 引用又要加上 readonly,所以才用 null 做了个区分。

传 null 就是 dom 引用,返回 RefObject,不传就是其他数据,返回 MutableRefObject。

所以,这就是一种约定,知道传 null 和不传 null 的区别就行了。

useCallback 、useMemo、useContext

useCallback 的类型参数是传入的函数的类型:

image.png

useMemo 的类型参数是传入的函数的返回值类型:

image.png

useContext 的类型参数是 Context 内容的类型:

image.png

当然,这些都没必要手动声明,用默认推导的就行。

EventHandler

很多时候,组件需要传入一些事件处理函数,比如 clickHandler:

import React, { HTMLAttributes, MouseEventHandler } from "react";

interface CccProps {
  clickHandler: MouseEventHandler<HTMLDivElement>
} 

function Ccc(props: CccProps) {
  return <div onClick={props.clickHandler}>ccc</div>
}

function App() {

  return <div>
    <Ccc clickHandler={(e) => {
      console.log(e);
    }}></Ccc>
  </div>
}

export default App;

这种参数就要用 xxxEventHandler 的类型,比如 MouseEventHandler、ChangeEventHandler 等,它的类型参数是元素的类型。

或者不用 XxxEventHandler,自己声明一个函数类型也可以:

image.png

// 鼠标事件
interface CccProps {
  clickHandler: (e: MouseEvent<HTMLDivElement>) => void
} 

// 输入框change事件
interface CccProps {
  clickHandler: (e: ChangeEvent<HTMLInputElement>) => void
} 

PropsWithChildren

前面讲过,jsx 类型用 ReactNode,比如这里的 content 参数:

image.png

如果你不想通过参数传入内容,可以在 children 里:

image.png

这时候就要声明 children 的类型为 ReactNode:

import React, { ReactNode } from "react";

interface CccProps {
  content: ReactNode,
  children: ReactNode
}

function Ccc(props: CccProps) {
  return <div>ccc,{props.content}{props.children}</div>
}

function App() {

  return <div>
    <Ccc content={<div>666</div>}>
      <button>7777</button>
    </Ccc>
  </div>
}

export default App;

但其实没有必要自己写,传 children 这种情况太常见了,React 提供了相关类型:

image.png

type CccProps = PropsWithChildren<{
  content: ReactNode,
}>

看下它的类型定义:

image.png

就是给 Props 加了一个 children 属性。