在React项目中优雅地使用Typescript

clipboard.png

「优雅」的含义:

  1. 减少编写冗余的类型定义、类型标注,充分利用ts的自动类型推断,以及外部提供的类型声明。
  2. 类型安全:提供足够的类型信息来避免运行时错误,让错误暴露在开发期。这些类型信息同时能够提供代码补全、跳转到定义等功能。

组件定义

函数组件

import * as React from 'react';
// 如果在tsconfig中设置了"allowSyntheticDefaultImports": true
// 你还可以更精练地import react:
// import React from "react";

interface IProps {
      // CSSProperties提供样式声明的类型信息
      // 用户传入style的时候就能够获得类型检查和代码补全
      style?: React.CSSProperties;
      // 使用@types/react提供的事件类型定义,这里指定event.target的类型是HTMLButtonElement
      onClick(event: React.MouseEvent<HTMLButtonElement>): void;
    // ...
}
const MyComponent: React.FC<IProps> = (props) => {
      const { children, ...restProps } = props;
    return <div {...restProps}>{children}</div>;
}
  1. FC是FunctionComponent的缩写。
  2. IProps无需声明children属性的类型。React.FC会自动为props添加这个属性类型。

    当然,如果children期望一个render prop,或者期望其他特殊的值,那么你还是要自己给children声明类型,而不是使用默认的React.ReactNode
  3. props无需做类型标注。
函数组件defaultProps(Deprecate)

如果你需要定义defaultProps,那么不要使用React.FC,因为React.FC对defaultProps的支持不是很好

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原生的参数解构+默认参数特性就已经能够满足需要:

const TestFunction: FunctionComponent<Props> = ({ foo = "bar" }) => <div>{foo}</div>

类组件

interface IProps {
  message: string;
}
interface IState {
  count: number;
}
export class MyComponent extends React.Component<IProps, IState> {
  state: IState = {
    // duplicate IState annotation for better type inference
    count: 0
  };
  render() {
    return (
      <div>
        {this.props.message} {this.state.count}
      </div>
    );
  }
}
  1. 如果你通过声明state属性来初始化state,那么你需要为这个属性增加IState类型标注。虽然这与前面的React.Component<IProps, IState>有重复的嫌疑,但是这两者实际上是不同的:

    • React.Component<IProps, IState>只是标注了基类的state属性类型。
    • 而当你在子类声明state时,你可以为state标注一个【IState的子类型】作为override。这样,this.state会以子类中的state属性声明作为类型信息的来源。
  2. 建议使用函数组件。

可渲染节点类型

可渲染节点就是:可以直接被组件渲染函数返回的值。

与可渲染节点有关的类型定义如下(摘录自@types/react):

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的时候经常用到。

    const withState = <P extends WrappedComponentProps>(
      WrappedComponent: React.ComponentType<P>,
    ) => { ...

获取并扩展原生元素的props类型

比如,以下例子获取并扩展了<button>的props类型:

export const PrimaryButton = (
  props: Props & React.HTMLProps<HTMLButtonElement>
) => <Button size={ButtonSizes.default} {...props} />;

PrimaryButton能够接受所有原生<button>所接受的props。关键在于React.HTMLProps

获取并扩展第三方组件的props类型

import { Button } from "library"; // but doesn't export ButtonProps! oh no!
type ButtonProps = React.ComponentProps<typeof Button>; // no problem! grab your own!
type AlertButtonProps = Omit<ButtonProps, "onClick">; // modify
const AlertButton: React.FC<AlertButtonProps> = props => (
  <Button onClick={() => alert("hello")} {...props} />
);

事件类型

@types/react提供了各种事件的类型,比如以下是使用React.FormEvent的例子:

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中,所有事件(包括FormEventKeyboardEventMouseEvent等)都是SyntheticEvent的子类型。他们在@types/react中定义如下:

// DOM事件的基本属性都定义在这里
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;
  // ...
}
// ...

参考资料


csRyan的学习专栏
分享对于计算机科学的学习和思考,只发布有价值的文章: 对于那些网上已经有完整资料,且相关资料已经整...

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart...

1.1k 声望
181 粉丝
0 条评论
推荐阅读
手写一个Parser - 代码简单而功能强大的Pratt Parsing
在编译的流程中,一个很重要的步骤是语法分析(又称解析,Parsing)。解析器(Parser)负责将Token流转化为抽象语法树(AST)。这篇文章介绍一种Parser的实现算法:Pratt Parsing,又称Top Down Operator Precede...

csRyan阅读 2.7k

手把手教你写一份优质的前端技术简历
不知不觉一年一度的秋招又来了,你收获了哪些大厂的面试邀约,又拿了多少offer呢?你身边是不是有挺多人技术比你差,但是却拿到了很多大厂的offer呢?其实,要想面试拿offer,首先要过得了简历那一关。如果一份简...

tonychen152阅读 17.8k评论 5

封面图
正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青56阅读 8.5k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy48阅读 7.1k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木75阅读 7.1k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 6.8k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木45阅读 8.6k评论 6

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart...

1.1k 声望
181 粉丝
宣传栏