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 为 MouseEventwindow.addEventListener("click", (e) => {  // ...});// e 为 DragEventwindow.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