业务开发所需的 TypeScript 常用技巧

React

React.FC

使用 React.FC 声明组件。

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

interface IHelloProps extends HTMLAttributes {
  name: string;
}

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

    • 注意: react@16 类型定义中 React.FC 自带 children 类型,无需额外处理(即可省略第 1 步)
  3. 若组件需要接受 html 属性,如 classNamestyle 等,可以直接 extends HTMLAttributes,其中 HTMLDivElement 可替换为所需要的类型,如 HTMLInputElement

React.forwardRef

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

import { forwardRef, PropsWithChildren } from "react";

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

export const FancyButton = forwardRef<
  HTMLButtonElement,
  PropsWithChildren
>((props, ref) => (
  
));

React.ComponentProps

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

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

type SemiDatePikerProps = React.ComponentProps;

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

  return ;
};

使用第三方库组件时,不要使用具体 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;

另外一个例子:

typescript-2

类型收窄

某些场景传入的参数为联合类型,需要基于一些手段将其类型收窄(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" }); // ×

多参数类型约束

以非常熟悉的 window.addEventListener 为例:

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

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

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

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

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

当然你也可以选择使用联合类型,就像 redux action 那样。

常用工具泛型

了解完 TypeScript 基础内容(keyof/in/extends/infer)后,可自行尝试实现内置工具泛型,实现一遍理解更深刻。
interface Person {
  name: string;
  age: number;
  address?: string;
}
  • Partial。将所有字段变为 optional
type PartialPerson = Partial;
// ↓
type PartialPerson = {
  name?: string | undefined;
  age?: number | undefined;
  address?: string | undefined;
};
  • Required。将所有字段变为 required
type RequiredPerson = Required;
// ↓
type RequiredPerson = {
  name: string;
  age: number;
  address: string;
};
  • Pick。从 T 中取出部分属性 K
type PersonWithoutAddress = Pick;
// ↓
type PersonWithoutAddress = {
  name: string;
  age: number;
};
  • Omit。从 T 中移除部分属性 K
type PersonWithOnlyAddress = Omit;
// ↓
type PersonWithOnlyAddress = {
  address?: string | undefined;
};
  • Exclude。从 T 中排除那些可分配给 U 的类型
该泛型实现需要掌握 Distributive Conditional Types
type T = Exclude<1 | 2, 1 | 3>; // -> 2
  • Extract。从 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;
// ↓
type T = [
  arg: {
    a: number;
    b: string;
  }
];
  • ReturnType。获取函数返回值类型
declare function f1(): { a: number; b: string };

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

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

业务开发所需的 TypeScript 常用技巧_第1张图片

推荐阅读

你可能感兴趣的:(业务开发所需的 TypeScript 常用技巧)