在 React 项目中优雅地使用 Typescript
TypeScript 是 Javascript 的超集,扩展了 JavaScript 的语法,给 JavaScript 带来了静态类型支持,了解如何在 React 项目中优雅地使用 Typescript,能帮助我们写出更优雅的代码。
「优雅」的含义:
- 减少编写冗余的类型定义、类型标注,充分利用ts的自动类型推断,以及外部提供的类型声明。
- 类型安全:提供足够的类型信息来避免运行时错误,让错误暴露在开发期。这些类型信息同时能够提供代码补全、跳转到定义等功能。
组件定义
函数组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import * as React from 'react';
interface IProps { style?: React.CSSProperties; onClick(event: React.MouseEvent<HTMLButtonElement>): void; } const MyComponent: React.FC<IProps> = (props) => { const { children, ...restProps } = props; return <div {...restProps}>{children}</div>; }
|
- FC是FunctionComponent的缩写。
- IProps无需声明children属性的类型。React.FC会自动为props添加这个属性类型。
当然,如果children期望一个render prop,或者期望其他特殊的值,那么你还是要自己给children声明类型,而不是使用默认
的React.ReactNode。
- props无需做类型标注。
函数组件defaultProps(Deprecate)
如果你需要定义defaultProps,那么不要使用React.FC,因为React.FC对defaultProps的支持不是很好:
1 2 3 4 5 6 7
| const defaultProps = { who: "Johny Five" }; type IProps = { age: number } & typeof defaultProps;
export const Greet = (props: IProps) => { return <div>123</div> }; Greet.defaultProps = defaultProps;
|
事实上,一个提议在函数组件中废弃defaultProps的React rfc已经被接受,所以以后还是尽量减少在函数组件上使用defaultProps,使用ES6原生的参数解构+默认参数特性就已经能够满足需要:
1
| const TestFunction: FunctionComponent<Props> = ({ foo = "bar" }) => <div>{foo}</div>
|
类组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface IProps { message: string; } interface IState { count: number; } export class MyComponent extends React.Component<IProps, IState> { state: IState = { count: 0 }; render() { return ( <div> {this.props.message} {this.state.count} </div> ); } }
|
- 如果你通过声明state属性来初始化state,那么你需要为这个属性增加IState类型标注。虽然这与前面的React.Component<IProps, IState>有重复的嫌疑,但是这两者实际上是不同的:
- React.Component<IProps, IState>只是标注了基类的state属性类型。
- 而当你在子类声明state时,你可以为state标注一个【IState的子类型】作为override。这样,this.state会以子类中的state属性声明作为类型信息的来源。
- 建议使用函数组件。
可渲染节点类型
可渲染节点就是:可以直接被组件渲染函数返回的值。
与可渲染节点有关的类型定义如下(摘录自[@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8a1b68be3a64e5d2aa1070f68cc935d668a976ad/types/react/index.d.ts#L187):
1 2 3 4 5
| type ReactText = string | number; type ReactChild = ReactElement | ReactText; interface ReactNodeArray extends Array<ReactNode> {} type ReactFragment = {} | ReactNodeArray; type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
|
组件类型
React.FC<Props>
(即 React.FunctionComponent<Props>
)
React.Component<Props, State>
React.ComponentType<Props>
(即ComponentClass<P> | FunctionComponent<P>
)
在写HOC的时候经常用到。
1 2 3
| const withState = <P extends WrappedComponentProps>( WrappedComponent: React.ComponentType<P>, ) => { ...
|
获取并扩展原生元素的props类型
比如,以下例子获取并扩展了<button>
的props类型:
1 2 3
| export const PrimaryButton = ( props: Props & React.HTMLProps<HTMLButtonElement> ) => <Button size={ButtonSizes.default} {...props} />;
|
PrimaryButton能够接受所有原生<button>
所接受的props
。关键在于React.HTMLProps
。
获取并扩展第三方组件的props类型
1 2 3 4 5 6
| import { Button } from "library"; type ButtonProps = React.ComponentProps<typeof Button>; type AlertButtonProps = Omit<ButtonProps, "onClick">; const AlertButton: React.FC<AlertButtonProps> = props => ( <Button onClick={() => alert("hello")} {...props} /> );
|
事件类型
@types/react
提供了各种事件的类型,比如以下是使用React.FormEvent
的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class App extends React.Component< {}, { text: string } > { state = { text: '', } onChange = (e: React.FormEvent<HTMLInputElement>): void => { this.setState({ text: e.currentTarget.value }) } render() { return ( <div> <input type="text" value={this.state.text} onChange={this.onChange} /> </div> ) } }
|
在React中,所有事件(包括FormEvent、KeyboardEvent、MouseEvent等)都是SyntheticEvent的子类型。他们在@types/react中定义如下:
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
| interface BaseSyntheticEvent<E = object, C = any, T = any> { nativeEvent: E; currentTarget: C; target: T; bubbles: boolean; cancelable: boolean; defaultPrevented: boolean; eventPhase: number; isTrusted: boolean; preventDefault(): void; isDefaultPrevented(): boolean; stopPropagation(): void; isPropagationStopped(): boolean; persist(): void; timeStamp: number; type: string; } interface SyntheticEvent<T = Element, E = Event> extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {}
interface FormEvent<T = Element> extends SyntheticEvent<T> {} interface KeyboardEvent<T = Element> extends SyntheticEvent<T, NativeKeyboardEvent> { altKey: boolean; } interface MouseEvent<T = Element, E = NativeMouseEvent> extends SyntheticEvent<T, E> { altKey: boolean; }
|