react中的内置ts类型

写工作台用ts,很多时候其实思路都水到渠成的,但是因为不清楚ts在react中有哪些内置类型,比如事件对象或者useRef()返回的类型,就会花大量时间去查,这里做一个小总结。

React.ReactNode和React.Element

这两个东西看了很多博客,都没得讲明白的,于是乎自己研究一下算了弄明白了区别,遇见这个问题的时候是写Radio组件,会用到React.Children.forEach()这个API,然后呢,就有了这个报错

node:element.png

为啥呢,就我们点进去看,就会发现,props.children的类型是

1
ReactNode | undefined

这就说明了<>xxx</>这里面的xxx基本都是ReactNode,那ReactNode又是啥?接着看源码

1
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

一个联合类型,boolean/null/undefined 不用管,我们看前三个,

ReactFragment和ReactPortal,这个好理解

1
type ReactFragment = {} | ReactNodeArray;

其实就是<>xxx</>或者<React.Fragament>xxx<React.Fragament/>

1
2
3
4
interface ReactPortal extends ReactElement {
key: Key | null;
children: ReactNode;
}

这个其实就是ReactDOM.createPortal(child, container)这个API的返回值嘛,看源码也知道

1
export function createPortal(children: ReactNode, container: Element, key?: null | string): ReactPortal;

然后ReactChild又是啥?

1
2
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

所以,看到没,从源码上看,ReactNode是一个联合类型,它囊括了ReactElement,也就是说如果你的类型是ReactElement,那么你就一定是ReactNode,扯了这么半天,那么究竟什么是ReactNode,什么是ReactElement呢,还是先从上面那个问题入手,根据上面的报错我们知道,child是ReactNode类型,所以需要用类型断言改变一下他的属性,那变成啥?

哎,我们就可以看看ReactElement的定义

1
2
3
4
5
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T;
props: P;
key: Key | null;
}

所以我们只要写上***(child as ReactElement).props***就不会报错了,那我们看看这个child是什么?

console.log()输出如下

这个是啥?一个对象? 我们知道哈,我们写的JSX,其实可以基于一个React创建VDOM的重要API——React.createElement()

那就看源码咯

1
2
3
4
function createElement<P extends {}>(
type: FunctionComponent<P> | ComponentClass<P> | string,
props?: Attributes & P | null,
...children: ReactNode[]): ReactElement<P>;

其他什么都别管,看最后一句,什么意思? ReactElement是啥,哎,是createElement的返回值!(这里要注意,并不是唯一的返回值,其实createElement的完整定义如下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// DOM Elements
// TODO: generalize this to everything in `keyof ReactHTML`, not just "input"
function createElement(
type: "input",
props?: InputHTMLAttributes<HTMLInputElement> & ClassAttributes<HTMLInputElement> | null,
...children: ReactNode[]): DetailedReactHTMLElement<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
function createElement<P extends HTMLAttributes<T>, T extends HTMLElement>(
type: keyof ReactHTML,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): DetailedReactHTMLElement<P, T>;
function createElement<P extends SVGAttributes<T>, T extends SVGElement>(
type: keyof ReactSVG,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): ReactSVGElement;
function createElement<P extends DOMAttributes<T>, T extends Element>(
type: string,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): DOMElement<P, T>;

// Custom components

function createElement<P extends {}>(
type: FunctionComponent<P>,
props?: Attributes & P | null,
...children: ReactNode[]): FunctionComponentElement<P>;
function createElement<P extends {}>(
type: ClassType<P, ClassicComponent<P, ComponentState>, ClassicComponentClass<P>>,
props?: ClassAttributes<ClassicComponent<P, ComponentState>> & P | null,
...children: ReactNode[]): CElement<P, ClassicComponent<P, ComponentState>>;
function createElement<P extends {}, T extends Component<P, ComponentState>, C extends ComponentClass<P>>(
type: ClassType<P, T, C>,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): CElement<P, T>;
function createElement<P extends {}>(
type: FunctionComponent<P> | ComponentClass<P> | string,
props?: Attributes & P | null,
...children: ReactNode[]): ReactElement<P>;

很头痛是吧?别急,如果我们在一个个点进这些类型声明中去看,会发现像什么 DOMElement<P, T>,FunctionComponentElement啊,CElement<P, ClassicComponent<P, ComponentState>>啊这些,他们其实都是 extends ReactElement。 所以得出结论——ReactElement是由React.createElement()这个API创建出来的类型,根本上是一个Js对象,如上图所示,在Jsx里面以自定义组件的形式呈现,比如<MyComponent />,这就是一个ReactElement,而<div />,也是一个ReactElement。

好,明白了ReactElement,那什么是ReactNode?

答案很简单——所有可以被React渲染到浏览器上的东西,也可以理解为,VDOM上的每个节点,都是是ReactNode。

好了 那么接下来看下面这部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Parent = () => {
return (
<>
<div>
<Child />
<div/>
</>
)
}

const Child = () => {
return (
<>
<div>
我是孩子
<div/>
</>
)
}

ReactDOM.render(
<Parent />,
document.getElementById('root')
)

自己思考一下?那些是ReactNode,那些是ReactElement呢 ?

注意一点,React.FC的返回值是ReactElement!不是ReactNode!这里很多博客说的不完全正确,我们可以看看源码

1
2
3
4
5
6
7
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P> | undefined;
contextTypes?: ValidationMap<any> | undefined;
defaultProps?: Partial<P> | undefined;
displayName?: string | undefined;
}

返回的类型是明确的ReactElement!为什么? 看之前createElement源码部分,有对自定义组件的说明。(当然其实也可以理解为返回ReactNode,因为Element是Node的子集,但还是根据源码准确点好, 因为可以注意类组件的render方法和函数声明方式写的组件,他们渲染的类型都是JSX.Element,也就是说 JSX.Element == ReactElement)

到这,虽然写的有点啰嗦,但我们要知其然并知其所以然嘛。

事件处理

事件处理其实主要就是event事件对象类型的问题

1
2
3
const onChange = (e) {
console.log(e.target.value)
}

受控表单一般都会这么写是吧,但如果你不给e限定类型他就会报错,说e这个类型没有target嘛,这个好解决,cmd点进去看这些合成事件就能看到,大部分啥ChangeEvent啊MouseEvent啊,还有一些泛型啥的,这个不难,简单的看一下实现,有兴趣就自己去研究下源码。

  • 剪切板事件对象:ClipboardEvent<T = Element>

  • 拖拽事件对象:DragEvent<T = Element>

  • 焦点事件对象:FocusEvent<T = Element>

  • 表单事件对象:FormEvent<T = Element>

  • Change事件对象:ChangeEvent<T = Element>

  • 键盘事件对象:KeyboardEvent<T = Element>

  • 鼠标事件对象:MouseEvent<T = Element, E = NativeMouseEvent>

  • 触摸事件对象:TouchEvent<T = Element>

  • 滚轮事件对象:WheelEvent<T = Element>

  • 动画事件对象:AnimationEvent<T = Element>

  • 过渡事件对象:TransitionEvent<T = Element>

就比如说我之前写受控表单的时候就是这么写的

Snipaste_2021-12-06_19-35-33

处理表单嘛,所以泛型给的就是HTMLInputElement,这个是HTML标签类型,放在后面讲。

这里注意到,onChange这个合成事件的类型是啥? 老长一串,哎,这也直接看简单的声明吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"];

type ReactEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;
// 剪切板事件处理函数
type ClipboardEventHandler<T = Element> = EventHandler<ClipboardEvent<T>>;
// 复合事件处理函数
type CompositionEventHandler<T = Element> = EventHandler<CompositionEvent<T>>;
// 拖拽事件处理函数
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
// 焦点事件处理函数
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
// 表单事件处理函数
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
// Change事件处理函数
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
// 键盘事件处理函数
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
// 鼠标事件处理函数
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
// 触屏事件处理函数
type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
// 指针事件处理函数
type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
// 界面事件处理函数
type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
// 滚轮事件处理函数
type WheelEventHandler<T = Element> = EventHandler<WheelEvent<T>>;
// 动画事件处理函数
type AnimationEventHandler<T = Element> = EventHandler<AnimationEvent<T>>;
// 过渡事件处理函数
type TransitionEventHandler<T = Element> = EventHandler<TransitionEvent<T>>;

编译器一般会有提示,vscode是有的,就比如我们写onChange类型的时候写个Change,后面的EventHandler应该会自动弹出来,实在不行也就直接cmd点进去看源码算了。

HTML标签类型

这个其实也好解释,我们在组件里面写的<div />啊 <input />啊啥的说到底,都是ReactElement嘛,不是真实DOM,就都是一个个VDOM对象,而这些VDOM同样需要标签属性啊,需要class,id,value。那就要给它们定义类型咯,这就是HTML标签类型。

然后所有的HTML标签类型都定义在了 node_moudles/@types/react/global.d.ts 这个文件里面,同样有兴趣自己去浏览一下,

常见的大概有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
a: HTMLAnchorElement;
body: HTMLBodyElement;
br: HTMLBRElement;
button: HTMLButtonElement;
div: HTMLDivElement;
h1: HTMLHeadingElement;
h2: HTMLHeadingElement;
h3: HTMLHeadingElement;
html: HTMLHtmlElement;
img: HTMLImageElement;
input: HTMLInputElement;
ul: HTMLUListElement;
li: HTMLLIElement;
link: HTMLLinkElement;
p: HTMLParagraphElement;
span: HTMLSpanElement;
style: HTMLStyleElement;
table: HTMLTableElement;
tbody: HTMLTableSectionElement;
video: HTMLVideoElement;
audio: HTMLAudioElement;
meta: HTMLMetaElement;
form: HTMLFormElement;

然后每个标签都还有自己的属性类型,不过用的不多,一般在自己封装原生组件的库的时候会用到(比如antd的Radio组件),简单了解下

1
2
3
4
5
6
7
8
9
10
11
12
13
HTML属性类型:HTMLAttributes
按钮属性类型:ButtonHTMLAttributes
表单属性类型:FormHTMLAttributes
图片属性类型:ImgHTMLAttributes
输入框属性类型:InputHTMLAttributes
链接属性类型:LinkHTMLAttributes
meta属性类型:MetaHTMLAttributes
选择框属性类型:SelectHTMLAttributes
表格属性类型:TableHTMLAttributes
输入区属性类型:TextareaHTMLAttributes
视频属性类型:VideoHTMLAttributes
SVG属性类型:SVGAttributes
WebView属性类型:WebViewHTMLAttributes

最后还有一些其他的,比如ref的类型啊或者useReducer,不过react提供的hooks一般会自动帮你进行类型推断,实在不行还是老办法,点进去看一眼类型源码就知道了。

综上,目前了解的就这么多了,如果以后学到新东西会来补充的,希望以后开发的时候不用在为连一些内置的类型都不知道发愁了。