写工作台用ts,很多时候其实思路都水到渠成的,但是因为不清楚ts在react中有哪些内置类型,比如事件对象或者useRef()返回的类型,就会花大量时间去查,这里做一个小总结。
React.ReactNode和React.Element
这两个东西看了很多博客,都没得讲明白的,于是乎自己研究一下算了弄明白了区别,遇见这个问题的时候是写Radio组件,会用到React.Children.forEach()这个API,然后呢,就有了这个报错
为啥呢,就我们点进去看,就会发现,props.children的类型是
这就说明了<>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
|
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>;
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>
就比如说我之前写受控表单的时候就是这么写的
处理表单嘛,所以泛型给的就是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>>;
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一般会自动帮你进行类型推断,实在不行还是老办法,点进去看一眼类型源码就知道了。
综上,目前了解的就这么多了,如果以后学到新东西会来补充的,希望以后开发的时候不用在为连一些内置的类型都不知道发愁了。