React

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. Use PropsWithChildren for IHelloProps inject children type
  2. Use React.FC to declare the component and pass in the component through the generic parameter Props type

    • Note: react@16 type definition React.FC comes with children type, no additional processing is required (step 1 can be omitted)
  3. html属性,如classNamestyle等,可以直接extends HTMLAttributes<HTMLDivElement> ,其中HTMLDivElement可Replace with the desired type, such as HTMLInputElement

React.FC is not recommended?

Remove React.FC from Typescript template #8177

In this PR, React.FC of the CRA default template has been removed, mainly for the following reasons:

  1. children are implicitly defined
  2. Cannot support generic components
  3. Mounting static properties is more complicated, such as <Select.Option>
  4. Problem with defaultProps

There is only one benefit:

  1. Return value constraints are provided

So you can choose whether or not to use React.FC. Generic components and mounting static properties belong to low-frequency scenarios. If you meet React.FC, you can do it~

As for defaultProps, it is basically no longer used in business code (replaced with default values), and the latest React.FC has removed the built-in children

If there are clear type requirements for the return value and typescript rules are configured, then React.FC can be used, and in other cases, the Props interface can be defined directly, as follows:

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

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

const Hello = ({ name, children, ...rest }: PropsWithChildren<IHelloProps>) => {
  return (
    <div>
      <div {...rest}>{`Hello, ${name}!`}</div>
      {children}
    </div>
  );
};

React.forwardRef

React provides forwardRef function for forwarding Ref, which can also pass in generic parameters, as follows:

 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

Tool generics for getting component props, similar to:

  • 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} />;
};

When using third-party library components, do not use specific paths to reference types (if the third-party components are subsequently upgraded to modify the internal file reference path, an error will occur).

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

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

If the entry file does not expose the relevant type declaration of the corresponding component, use React.ComponentProps

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

type InputProps = React.ComponentProps<typeof Input>;

Another example:

typescript-2

Type narrows

In some scenarios, the parameter passed in is a union type, and its type needs to be narrowed (Narrowing) based on some means.

 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);
  }
}

Use type predicates: is

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

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

Think about Lodash's isBoolean / isString / isArray and other functions, and then think about what is wrong with 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; // 这里的定义会使得业务中时使用出现什么问题?
}

Type-safe redux actions

The author does not use redux, here is only a demonstration

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" }); // ×

Multi-parameter type constraints

Take the very familiar window.addEventListener as an example:

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

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

It can be found that the input parameter type (event) of the callback function of addEventListener will vary with the listening event. The function signature of addEventListener is as follows:

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

type is the generic type K, which is constrained to the key range of WindowEventMap , and then deduce the ev event type from WindowEventMap based on K.

Of course you can also choose to use union types, like redux actions do.

Common tool generics

After understanding the basic content of TypeScript (keyof/in/extends/infer), you can try to implement the built-in tool generics by yourself to achieve a deeper understanding.
 interface Person {
  name: string;
  age: number;
  address?: string;
}
  • Partial<T>. make all fields optional
 type PartialPerson = Partial<Person>;
// ↓
type PartialPerson = {
  name?: string | undefined;
  age?: number | undefined;
  address?: string | undefined;
};
  • Required<T>. Make all fields required
 type RequiredPerson = Required<Person>;
// ↓
type RequiredPerson = {
  name: string;
  age: number;
  address: string;
};
  • Pick<T, K extends keyof T>. Take part of the attribute K from T
 type PersonWithoutAddress = Pick<Person, "name" | "age">;
// ↓
type PersonWithoutAddress = {
  name: string;
  age: number;
};
  • Omit<T, K extends keyof T>. remove part of attribute K from T
 type PersonWithOnlyAddress = Omit<Person, "name" | "age">;
// ↓
type PersonWithOnlyAddress = {
  address?: string | undefined;
};
  • Exclude<T, U>. exclude from T those types assignable to U
The generic implementation needs to master Distributive Conditional Types
 type T = Exclude<1 | 2, 1 | 3>; // -> 2
  • Extract<T, U>. extract those types from T that are assignable to U
The generic implementation needs to master Distributive Conditional Types
 type T = Extract<1 | 2, 1 | 3>; // -> 1
  • Parameters. Get function parameter type
 declare function f1(arg: { a: number; b: string }): void;

type T = Parameters<typeof f1>;
// ↓
type T = [
  arg: {
    a: number;
    b: string;
  }
];
  • ReturnType. Get function return value type
 declare function f1(): { a: number; b: string };

type T = ReturnType<typeof f1>;
// ↓
type T = {
  a: number;
  b: string;
};
  • Record<K, T>. Convert the values of all attributes in K to type T

Understand each tool generic as a function, and the type can be used as the input parameter and return value. You can also read the specific implementation by cmd + left-click on the specific tool generic.

typescript-1

Recommended reading


海秋
311 声望18 粉丝

前端新手