Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Typescript has become so popular that I’ve listed some of the most difficult scenarios and solutions for those trying to write Typescript types well

The following scenarios are examples of scenarios that have been encountered in actual requirements and in historical code over time, some of which may be simple, but occur more often than not, and some of which are personally difficult. You are also welcome to comment on any problems you have encountered or to suggest different ideas in the comments section

Convert JSON data into interface type definitions using an online site

Scenario: A large number of fields are manually entered into the interface type.

Json2ts can help you reduce this part of your work

On the Mac, hold down the Option key to view the properties defined by the interface type

Scenario: Place the mouse directly over the type name. You can see only the type name and comments, not the attribute name.

Just hold down the Option key at the same time to see the property name!!

I won’t be the last one to know about this!!

Use index signatures to support any number of attributes

Scenario: There is a type, only a few explicit properties know the type, and a few properties do not know the type.

Can we only define the entire type as any? No ~

Indexable types solve this problem.

SquareConfig can have any number of attributes, and as long as they are not color or width, it doesn't matter what type they are
interfaceSquareConfig { color? :string; width? :number;

    // Indexable type. This object may have some extra properties that are used for special purposes
    [propName: string] :any;
}
Copy the code

Use generics to regulate a bunch of similar types

Scenario: The list interface returned by the back end is the same structure, but the specific list item type is different. It is too cumbersome to define a return type each time.

That’s when you need a paradigm to help you out.

// Real bad examples
interface Result {
  total: number;
  list: any[];
}
// This is a little better, with the list qualified
// But why not? Because each list item repeatedly defines a return structure...
interface Result1 {
  total: number;
  list: VO1[];
}
interface Result2 {
  total: number;
  list: VO2[];
}
// ...

// Use the template, more specification
export interface ListResult<T> {
  list: T[];
  total: number;
}
// Just write ListResult
      
interface Result3 {
  total: number;
  list: VO[];
}
Copy the code

Extract the props of the React component using ComponentProps

Scenario: To extend the Antd Select component, use the props method to extract the props type of the Select component.

The previous approach might have been:

// Aggressive player: what expansion? I just define my own properties
interfaceNewSelectorProps1 { test? :string; // Add a new field after the extension
}
// Slinky player: Index type, okay?
interfaceNewSelectorProps1 { test? :string; // Add a new field after the extension
  [propName: string] :any;
}
// Class type player: inherit, right?
interface NewSelectorProps2 extendsSelectProps { test? :string; // Add a new field after the extension
}
Copy the code

Another way to do it:

interface NewSelectorProps extendsComponentProps<typeof Select> { test? :string; // Add a new field after the extension
}

// ComponentProps
type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =
    T extends JSXElementConstructor<infer P>
        ? P
        : T extends keyof JSX.IntrinsicElements
            ? JSX.IntrinsicElements[T]
            : {};
Copy the code

What’s the good of that?

  1. It is also possible to define the props type of the extended component if you do not know the props type of the extended component
    1. Some components do not expose the PROPS type definition
    2. Some components expose types that do not match the actual type
    3. Components that need to be extended are dynamic, such as HOC scenarios
  2. Save the hassle of checking what type the component is exposed to

If there are any hard wounds that are not recommended, please let me know in the comments section

Use Omit to modify a property of an interface

Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit Omit

interface FormVO {
  test: number;
  / /... Other property definitions that do not need to be changed
}
export interface IForm extends FormVO<FormVO, 'test'> {
  test: string; // Change test to string only
}
Copy the code

Customize the return value of a hook with a const assertion

Scenario: a hook is defined, and more than one value is returned. As a result, it takes time and effort to define the type of each item, otherwise the type cannot be correctly identified during the value.

Const assertions are released in version 3.4 and represent type assertions — literal types in this expression should not be extended;

// A custom hook example
  // Indicates the type
  // let map: {};
  // let updateMap: () => {};
function useTest1() {
  return [map, updateMap]; // Export directly
}
function useTest2() {
  return [map, updateMap] as [object.() = > void]; // Define type export, time-consuming and laborious
}
function useTest3() {
  return [map, updateMap] as const; // Use const assertion
}

const [map1, updateMap1] = useTest1();
const [map2, updateMap2] = useTest1();
const [map3, updateMap3] = useTest1();
// The inferred types are
// map1: object | () => void; updateMap1: object | () => void;
// map2: object; updateMap2: () => void;
// map3: object; updateMap3: () => void;
Copy the code

Killer TypeScript feature: Const assertion

Using overloads to define the same function returns different values for different input arguments

Scenario: A function that returns an object if a second argument is passed, or a single value otherwise

// One way to write it
interfaceTest { <T>(a: T, isObj? :boolean): T | object;
};
// Another way to write it
interface Test {
  <T>(a: T): T;
  <T>(a: T, isObj: boolean) :object;
};
Copy the code

Use type protection to specify types

Scenario: Sometimes the input type of a method is uncertain, but needs to be treated differently for different types.

In this scenario we need to use user-defined type protection to specify the type of the parameter.

interface Foo {
  test1: string;
}
interface Bar {
  test2: number;
}
// a normal detection function
function isFoo1(arg: Foo | Bar) {
  return (arg asFoo).test1 ! = =undefined;
}
// use a is B custom type protection, if the return value is true, then a is type B
function isFoo2(arg: Foo | Bar) :arg is Foo{
  return (arg asFoo).test1 ! = =undefined;
}
function test1(arg: Foo | Bar) {
  if (isFoo1(arg)) {
    console.log(arg.test1); // Attribute "test1" does not exist on Error type "Bar"
    console.log(arg.test2); // Attribute "test2" does not exist on Error type "Foo"
  } else {
    console.log(arg.test1); // Error
    console.log(arg.test2); // Error}}function test2(arg: Foo | Bar) {
  if (isFoo2(arg)) {
    console.log(arg.test1); // ok
    console.log(arg.test2); // Error attribute "test2" does not exist on type "Foo". Do you mean "test1"?
  } else {
    console.log(arg.test1); // Error attribute "test1" does not exist on type "Bar". Do you mean "test2"?
    console.log(arg.test2); // ok}}Copy the code

Other built-in type protections are: instanceof typeof in literal type type protection