The following questions are from the discussion with company partners and net friends, sorted into chapters, hoping to provide another way of thinking (to avoid stepping on the pit) to solve the problem.

Function overloading

TypeScript provides function overloading to handle scenarios where the return type of a function is different depending on its parameters. You only need to define multiple types for the same function, as shown below:

declare function test(a: number) :number;
declare function test(a: string) :string;

const resS = test('Hello World');  // resS is inferred to be of type string;
const resN = test(1234);           // resN is inferred to be of type number;
Copy the code

It can also be used in scenarios with different parameters and the same return value type, so we just need to know which parameters can be used under which function type definition.

Consider the following example:

interface User {
  name: string;
  age: number;
}

declare function test(para: User | number, flag? :boolean) :number;
Copy the code

In the test function, we probably intended to pass no flag when para is User and flag when para is number. TypeScript doesn’t know this. Flag also allows you to pass in para as User:

const user = {
  name: 'Jack',
  age: Awesome!
}

// No error reported, but against the idea
const res = test(user, false);
Copy the code

Using function overloading can help us achieve:

interface User {
  name: string;
  age: number;
}

declare function test(para: User) :number;
declare function test(para: number, flag: boolean) :number;

const user = {
  name: 'Jack',
  age: Awesome!
};

// bingo
// Error: Parameter mismatch
const res = test(user, false);
Copy the code

In a real project, you might want to write a few more steps, as in class:

interface User {
  name: string;
  age: number;
}

const user = {
  name: 'Jack',
  age: 123
};

class SomeClass {

  /** * note 1 */
  public test(para: User): number;
  /** * note 2 */
  public test(para: number, flag: boolean) :number;
  public test(para: User | number, flag? :boolean) :number {
    // Implement it
    return 11; }}const someClass = new SomeClass();

// ok
someClass.test(user);
someClass.test(123.false);

// Error
someClass.test(123);
someClass.test(user, false);
Copy the code

Mapping type

Mapping types have been refined and enhanced since TypeScript 2.1 came out. In version 2.1, you can use keyof to get the key type of an object, and built-in Partial, Readonly, Record, and Pick mappings. ThisType added in version 2.3; Version 2.8 added Exclude, Extract, NonNullable, ReturnType, InstanceType; At the same time, add conditional types and enhance keyof capabilities in this version; Version 3.1 supports mapping tuples to arrays. All of this means that mapping types play an important role in TypeScript.

ThisType, which does not appear in the official documentation, is mainly used to type this in object literals:

// Compile with --noImplicitThis

typeObjectDescriptor<D, M> = { data? : D; methods? : M & ThisType<D & M>;// Type of 'this' in methods is D & M
}

function makeObject<D.M> (desc: ObjectDescriptor<D, M>) :D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return{... data, ... methods }as D & M;
}

let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx;  // Strongly typed this
      this.y += dy;  // Strongly typed this}}}); obj.x =10;
obj.y = 20;
obj.moveBy(5.5);
Copy the code

ThisType is the reason Vue 2.5 has enhanced TypeScript support.

There are many mapping types built in, but in many cases we need to customize the mapping type for our own project:

For example, you might want to retrieve function types from interface types:

type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

interface Part {
  id: number;
  name: string;
  subparts: Part[];
  updatePart(newName: string) :void;
}

type T40 = FunctionPropertyNames<Part>;  // "updatePart"
type T42 = FunctionProperties<Part>;     // { updatePart(newName: string): void }
Copy the code

For example, you might alias a method that belongs to one property somewhere else in some way for convenience.

For example, SomeClass has an attribute value = [1, 2, 3]. You might add this functionality to your class in Decorators: When you call this.find() in SomeClass, you’re actually calling this.value.find(), but TypeScript doesn’t know that:

class SomeClass {
  value = [1.2.3];

  someMethod() {
    this.value.find(/ *... * /);  // ok
    this.find(/ *... * /);        // Error: SomeClass does not have a find method.}}Copy the code

With the help of mapping types and interface + class declarations, we can achieve our objectives:

type ArrayMethodName = 'filter' | 'forEach' | 'find';

type SelectArrayMethod<T> = {
 [K in ArrayMethodName]: Array<T>[K]
}

interface SomeClass extends SelectArrayMethod<number> {}

class SomeClass {
 value = [1.2.3];

 someMethod() {
   this.forEach(/ *... * /)        // ok
   this.find(/ *... * /)           // ok
   this.filter(/ *... * /)         // ok
   this.value                     // ok
   this.someMethod()              // ok}}const someClass = new SomeClass();
someClass.forEach(/ *... * /)        // ok
someClass.find(/ *... * /)           // ok
someClass.filter(/ *... * /)         // ok
someClass.value                     // ok
someClass.someMethod()              // ok
Copy the code

It can also be used when exporting the SomeClass class.

Where it might be a bit of a weakness, in this code interface SomeClass extends SelectArrayMethod

{} you need to manually add the specific type of the stereotype (I haven’t thought of a better way yet).

Types of assertions

Type assertions are used to explicitly tell TypeScript the specific type of a value, and are used wisely to reduce our workload.

For example, if a variable has no initial value, but we know its type information (it may be returned from the back end), how can we deduce the type information correctly and still function correctly? One online recommendation is to set the initial value and then use Typeof to retrieve the type (which may be used elsewhere). However, I may be lazy and don’t like setting initial values, so using type assertions can solve this problem:

interface User {
  name: string;
  age: number;
}

export default class NewRoom extends Vue {
  private user = {} as User;
}
Copy the code

By adding an assertion when setting initialization, we don’t need to add an initial value and the editor will prompt the code as usual. If you have a lot of user attributes, you can take care of a lot of unnecessary work and define interfaces that can be used elsewhere.

Enumerated type

Enumerations are divided into numeric types and string types. Enumerations of numeric types can be used as flags:

// https://github.com/Microsoft/TypeScript/blob/master/src/compiler/types.ts#L3859
export const enum ObjectFlags {
  Class            = 1 << 0.// Class
  Interface        = 1 << 1.// Interface
  Reference        = 1 << 2.// Generic type reference
  Tuple            = 1 << 3.// Synthesized generic tuple type
  Anonymous        = 1 << 4.// Anonymous
  Mapped           = 1 << 5.// Mapped
  Instantiated     = 1 << 6.// Instantiated anonymous or mapped type
  ObjectLiteral    = 1 << 7.// Originates in an object literal
  EvolvingArray    = 1 << 8.// Evolving array type
  ObjectLiteralPatternWithComputedProperties = 1 << 9.// Object literal pattern with computed properties
  ContainsSpread   = 1 << 10.// Object literal contains spread operation
  ReverseMapped    = 1 << 11.// Object contains a property from a reverse-mapped type
  JsxAttributes    = 1 << 12.// Jsx attributes type
  MarkerType       = 1 << 13.// Marker type used for variance probing
  JSLiteral        = 1 << 14.// Object type declared in JS - disables errors on read/write of nonexisting members
  ClassOrInterface = Class | Interface
}
Copy the code

The TypeScript SRC/Compiler /types source code defines a number of enumerations of constants based on numeric types as shown above. They are an efficient way to store and represent a set of Boolean values.

An example of how to use TypeScript is provided in Understanding TypeScript:

enum AnimalFlags {
  None        = 0,
  HasClaws    = 1 << 0,
  CanFly      = 1 << 1,
  HasClawsOrCanFly = HasClaws | CanFly
}

interface Animal {
  flags: AnimalFlags;
  [key: string] :any;
}

function printAnimalAbilities(animal: Animal) {
  var animalFlags = animal.flags;
  if (animalFlags & AnimalFlags.HasClaws) {
    console.log('animal has claws');
  }
  if (animalFlags & AnimalFlags.CanFly) {
    console.log('animal can fly');
  }
  if (animalFlags == AnimalFlags.None) {
    console.log('nothing'); }}var animal = { flags: AnimalFlags.None };
printAnimalAbilities(animal); // nothing
animal.flags |= AnimalFlags.HasClaws;
printAnimalAbilities(animal); // animal has claws
animal.flags &= ~AnimalFlags.HasClaws;
printAnimalAbilities(animal); // nothing
animal.flags |= AnimalFlags.HasClaws | AnimalFlags.CanFly;
printAnimalAbilities(animal); // animal has claws, animal can fly
Copy the code

| = in the example code used to add a logo, & = and ~ used to delete logo, | to merge.