乐趣区

关于typescript:业务开发所需的-TypeScript-常用技巧

React

React.FC

应用 React.FC 申明组件。

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

interface IHelloProps extends HTMLAttributes<HTMLDivElement> {name: string;}

const Hello: React.FC<PropsWithChildren<IHelloProps>> = ({
  name,
  children,
  ...rest
}) => {
  return (
    <div>
      <div {...rest}>{`Hello, ${name}!`}</div>
      {children}
    </div>
  );
};
  1. 应用 PropsWithChildrenIHelloProps 注入 children 类型
  2. 应用 React.FC 申明组件,通过泛型参数传入组件 Props 类型

    • 留神:react@16 类型定义中 React.FC 自带 children 类型,无需额定解决(即可省略第 1 步)
  3. 若组件须要承受 html 属性,如 classNamestyle 等,能够间接 extends HTMLAttributes<HTMLDivElement>,其中 HTMLDivElement 可替换为所须要的类型,如 HTMLInputElement

React.forwardRef

React 提供了 forwardRef 函数用于转发 Ref,该函数也可传入泛型参数,如下:

import {forwardRef, PropsWithChildren} from "react";

interface IFancyButtonProps {type: "submit" | "button";}

export const FancyButton = forwardRef<
  HTMLButtonElement,
  PropsWithChildren<IFancyButtonProps>
>((props, ref) => (<button ref={ref} className="MyClassName" type={props.type}>
    {props.children}
  </button>
));

React.ComponentProps

用于获取组件 Props 的工具泛型,与之类似的还有:

  • React.ComponentPropsWithRef
  • React.ComponentPropsWithoutRef
import {DatePicker} from "@douyinfe/semi-ui";

type SemiDatePikerProps = React.ComponentProps<typeof DatePicker>;

export const DisabledDatePicker: React.FC = () => {const disabledDate: SemiDatePikerProps["disabledDate"] = (date) => {// ...};

  return <DatePicker disabledDate={disabledDate} />;
};

应用第三方库组件时,不要应用具体 path 去援用类型(若第三方组件后续降级批改了外部文件援用门路,会呈现谬误)。

import {InputProps} from "@douyinfe/semi-ui/input"; // ×

import {InputProps} from "@douyinfe/semi-ui"; // √

若入口文件未裸露对应组件的相干类型申明,应用 React.ComponentProps

import {Input} from "@douyinfe/semi-ui";

type InputProps = React.ComponentProps<typeof Input>;

另外一个例子:

类型收窄

某些场景传入的参数为联结类型,须要基于一些伎俩将其类型收窄(Narrowing)。

function printAll(strs: string | string[] | null) {if (strs && typeof strs === "object") {// strs 为 string[]
    for (const s of strs) {console.log(s);
    }
  } else if (typeof strs === "string") {
    // strs 为 string
    console.log(strs);
  }
}

is 操作符

function isFish(pet: Fish | Bird): pet is Fish {return (pet as Fish).swim !== undefined;
}

if (isFish(pet)) {pet.swim();
} else {pet.fly();
}

思考一下 Lodash 的 isBoolean/isString/isArray… 等函数,再思考一下应用 isEmpty 有什么不对。

interface LoDashStatic {isBoolean(value?: any): value is boolean;
  isString(value?: any): value is string;
  isArray(value?: any): value is any[];
  isEmpty(value?: any): boolean; // 这里的定义会使得业务中时应用呈现什么问题?}

类型平安的 redux action

笔者不必 redux,此处仅做演示

TS Playground – An online editor for exploring TypeScript and JavaScript

interface ActionA {
  type: "a";
  a: string;
}

interface ActionB {
  type: "b";
  b: string;
}

type Action = ActionA | ActionB;

function reducer(action: Action) {switch (action.type) {
    case "a":
      return console.info("action a:", action.a);
    case "b":
      return console.info("action b:", action.b);
  }
}

reducer({type: "a", a: "1"}); // √
reducer({type: "b", b: "1"}); // √

reducer({type: "a", b: "1"}); // ×
reducer({type: "b", a: "1"}); // ×
  • How to type Redux actions and Redux reducers in TypeScript?
  • 更多收窄形式可参考官网文档 – TypeScript Documentation – Narrowing

多参数类型束缚

以十分相熟的 window.addEventListener 为例:

// e 为 MouseEvent
window.addEventListener("click", (e) => {// ...});

// e 为 DragEvent
window.addEventListener("drag", (e) => {// ...});

能够发现 addEventListener 的回调函数入参类型(event)会随着监听事件的不同而不同,addEventListener 的函数签名如下:

addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;

type 为泛型 K,束缚在 WindowEventMap 的 key 范畴内,再基于 K 从 WindowEventMap 推导出 ev 事件类型即可。

当然你也能够抉择应用联结类型,就像 redux action 那样。

  • TypeScript 类型技巧 – 多参数类型束缚

常用工具泛型

理解完 TypeScript 根底内容(keyof/in/extends/infer)后,可自行尝试实现内置工具泛型,实现一遍了解更粗浅。

interface Person {
  name: string;
  age: number;
  address?: string;
}
  • Partial<T>。将所有字段变为 optional
type PartialPerson = Partial<Person>;
// ↓
type PartialPerson = {
  name?: string | undefined;
  age?: number | undefined;
  address?: string | undefined;
};
  • Required<T>。将所有字段变为 required
type RequiredPerson = Required<Person>;
// ↓
type RequiredPerson = {
  name: string;
  age: number;
  address: string;
};
  • Pick<T, K extends keyof T>。从 T 中取出局部属性 K
type PersonWithoutAddress = Pick<Person, "name" | "age">;
// ↓
type PersonWithoutAddress = {
  name: string;
  age: number;
};
  • Omit<T, K extends keyof T>。从 T 中移除局部属性 K
type PersonWithOnlyAddress = Omit<Person, "name" | "age">;
// ↓
type PersonWithOnlyAddress = {address?: string | undefined;};
  • Exclude<T, U>。从 T 中排除那些可调配给 U 的类型

该泛型实现须要把握 Distributive Conditional Types

type T = Exclude<1 | 2, 1 | 3>; // -> 2
  • Extract<T, U>。从 T 中提取那些可调配给 U 的类型

该泛型实现须要把握 Distributive Conditional Types

type T = Extract<1 | 2, 1 | 3>; // -> 1
  • Parameters。获取函数入参类型
declare function f1(arg: { a: number; b: string}): void;

type T = Parameters<typeof f1>;
// ↓
type T = [
  arg: {
    a: number;
    b: string;
  }
];
  • ReturnType。获取函数返回值类型
declare function f1(): { a: number; b: string};

type T = ReturnType<typeof f1>;
// ↓
type T = {
  a: number;
  b: string;
};
  • Record<K, T>。将 K 中所有的属性的值转化为 T 类型

把一个个工具泛型了解成函数,类型作为入参和返回值即可,通过 cmd + 左键点击具体工具泛型浏览具体实现也可。

  • 工具泛型具体实现请浏览:TS 一些工具泛型的应用及其实现
  • 更多内置工具泛型可参考:TypeScript Documentation – Utility Types

举荐浏览

  • TS 一些工具泛型的应用及其实现
  • 22 个示例深刻解说 Ts 最艰涩难懂的高级类型工具
  • The minimum TypeScript you need for React
  • tsconfig 罕用配置解析
  • TypeScript 类型技巧 – 多参数类型束缚
  • Typescript Tips: 动静重载实现廉价版 dependent type
退出移动版