TypeScript Getting started notes

This article is a summary of some TS basics and an interpretation of the official built-in tools Pick, Partial, diff

Build a typescript environment

Install typescript globally and initialize tsconfig.js

NPM install -g typescript NPM init // generates ts.config.js file TSC --initCopy the code

Initial Configuration

Configure some common NPM commands in package.json as well as some COMPILATION options for TS

  • Configure script in package.json

    {
      "main": "src/index.ts"."scripts": {
        "build": "tsc"./ / compile
        "build:w": "tsc -w" // Listen to the file, if there are changes, then compile}}Copy the code
  • Tsconfig. Json configuration

  "compilerOptions": {
    "target": "es5".// Specify ECMAScript target version: 'ES5'
    "module": "commonjs".// Specify the module to use: 'commonJS ',' AMD ', 'system', 'umd' or 'es2015'
    "moduleResolution": "node".// Select the module resolution policy
    "experimentalDecorators": true.// Enable experimental ES decorator
    "allowSyntheticDefaultImports": true.// Allow default imports from modules that do not have default exports set.
    "sourceMap": true.// When compiling ts files into js files, generate the corresponding map file
    "strict": true.// Enable all strict type checking options
    "noImplicitAny": true.// There is an error with an implied any type on expressions and declarations
    "alwaysStrict": true.// Check modules in strict mode and add 'use strict' to each file
    "declaration": true.// Generate the corresponding.d.ts file
    "removeComments": true.// Delete all comments after compilation
    "noImplicitReturns": true.// Not all return paths of the function have return value error
    "importHelpers": true.// Import helper functions from tslib
    "lib": ["es6"."dom"].// Specify the library file to be included in the build
    "typeRoots": ["node_modules/@types"]."outDir": "./dist"."rootDir": "./src"
  },
  "include": [                                  // ts files to compile a * indicates file matching ** indicates ignoring file depth issues
    "./src/**/*.ts"]."exclude": [
    "node_modules"."dist"."**/*.test.ts"]},Copy the code

TypeScript basics

Types in TS (any,unknown,never,enum)

  • any

    Specify a type for variables whose type is not known at programming time. These values may come from dynamic content, such as user input or third-party code libraries. Using any allows them to pass compile-time checks directly

  • unknown

    The main difference between Unknown and any is that the unknown type is more rigorous: some kind of check must be performed before most operations are performed on values of unknown type, whereas no check is required before operations are performed on values of any type

    Here’s the difference between unknown and any. First, they can both be of any type:

    any

      let value: any;
      value = true;             // OK
      value = 1;                // OK
      value = "Hello World";    // OK
    Copy the code

    unknown

      let value: unknown;
      value = true;             // OK
      value = 1;                // OK
    Copy the code

See what the difference is:

  let value: any;
  value.foo.bar;  // OK
  value();        // OK
  new value();    // OK


Copy the code

If the type is unknown

  let value: unknown;
  value.foo.bar;  // ERROR
  value();        // ERROR
  new value();    // ERROR
Copy the code

As you can see, although they can be of any type, unknown types cannot be instantiated, getters, function execution, etc., until they are determined to be of a type.

Any is ok. That’s why unknown is a safer any

  • never

    The never type represents the types of values that never exist. The never type is a subtype of any type and can be assigned to any type; However, no type is a subtype of never or can be assigned to a type of never (except never itself).

    Function error(message: string): never {throw new error(message); } // Empty array, and always empty const empty: never[] = []Copy the code
  • object

    Object represents a non-primitive type, that is, a type other than number, string, Boolean, symbol, NULL, or undefined.

    
    enum Direction {
        Center = 1
    }
    
    let value: object
    
    value = Direction    // OK
    value = [1]          // OK
    value = [1.'hello'] // OK
    value = {}           // OK    
    
    Copy the code
  • enum

An enumerated type is a type that many languages have that declares a set of named constants and can be defined as an enumerated type when a variable has several possible values.

// Enumeration of numbers
// When an enumerated type is declared, its value is the default numeric type, although no value is assigned to it, and it is accumulated from 0 by default
enum Direction {
    Up,
    Down,
    Left,
    Right
}

console.log(Direction.Up === 0); // true
console.log(Direction.Down === 1); // true

// String enumeration
enum Direction {
    Up = 'Up',
    Down = 'Down',}Copy the code

Enumerations have the ability to map backwards, so take a look at what enumerations look like when compiled into JavaScript:

// Reverse mapping enum Direction {Up, Down, Left, Right} console.log(Direction[0]); Var Direction; (function (Direction) { Direction[Direction["Up"] = 10] = "Up"; Direction[Direction["Down"] = 11] = "Down"; Direction[Direction["Left"] = 12] = "Left"; Direction[Direction["Right"] = 13] = "Right"; })(Direction || (Direction = {}));Copy the code

You can think of an enumerated type as a JavaScript object, but because of its special construction, it has the property of simultaneous forward and backward mapping

Generic constraints, indexable types

  1. Generic constraint

    You can constrain generics with

    , which is one of number or string. An error is reported when a Boolean type is passed

    type Params = nubmer | string
    function test<t extends Params>(params:T): T {
        return T
    }
    Copy the code
  2. The index sign

    Index signature includes numeric index and string index. Its syntax is as follows:

    [prop]:string :any | [prop]:number :any

    // The interface declares that the index of the object can be any string, Inteface Obj {name:string, [prop]:string :any} let Obj :Obj ={name:'123', age:2323,}Copy the code

Type derivation, type assertion, assignment assertion, type guard,

  1. Type inference

    In TypeScript, type inference helps provide types where they are not explicitly specified. Here’s an example

    // there is no declaration of type x,ts is automatically derived to type number
    let x = 3;
    x = '123' // Error: 'string' cannot be assigned to 'number'
    
    // The result is
    const bar = [1.2];
    let [a, b] = bar;
    a = 'hello'; // Error: 'string' cannot be assigned to 'number'
    
    Copy the code
  2. Type Assertion (AS)

    In cases where TS does not correctly or accurately infer the type, unnecessary warnings or errors may be generated.

    Such as:

    const person = {}; person.name = 'zhangsan'; // Error: 'name' attribute does not exist in '{}' person.age = 20; // Error: 'age' does not exist in '{}' // Type assertion interface Person {name: string; age: number; } const person = {} as Person;Copy the code

    Because of type inference, the type of Person is {} and there are no additional attributes, so the developer needs to use AS to determine what type the variable is

    3. Assignment assertion (!)

    Explicit assignment assertion is a feature that allows you to put! After the instance property and variable declaration to indicate that the property is determined to have been assigned:

        let x: number;
        initialize();
        console.log(x + x); // The variable "x" is used before the assignment.
        function initialize() {
            x = 10;
        }
    
    Copy the code

    Use! To indicate that the property is certain that it has been assigned

    let x: number; initialize(); console.log(x! + x!) ; //ok function initialize() { x = 10; }Copy the code

    4. Type guard

    In fact, type guard means to narrow the scope of the type, let’s see a few examples

    class Person {
        name = 'xiaomuzhu';
        age = 20;
    }
    class Animal {
        name = 'petty';
        color = 'pink';
    }
    // Type refinement via instanceof, in statements
    function getSometing(arg: Person | Animal) {
        // The type is refined to Person
        if (arg instanceof Person) {
        }
        // The type is refined to Person
        if (arg instanceof Animal) {
        }
        if ('age' in arg) {}
    }
    Copy the code

Cross type, union type, type alias, identifiable union type

1. Cross types

Interface IAnyObject {[prop: string]: {' T & U ': {' T & U' : {' T & U ': { any } function mixin<T extends IAnyObject, U extends IAnyObject>(first: T, second: U): T & U { const result = <T & U>{}; for (let id in first) { (<T>result)[id] = first[id]; } for (let id in second) { if (! result.hasOwnProperty(id)) { (<U>result)[id] = second[id]; } } return result; } const x = mixin({ a: 'hello' }, { b: 42 }); // now x has a const a = x.a; const b = x.b; ` ` `Copy the code
  1. The joint type

    Typeof type protection is used to distinguish between types

    // The argument may be passed in two types. Use if to narrow down the type range
    function formatCommandline(command: string[] | string) {
      let line = ' ';
      if (typeof command === 'string') {
        line = command.trim();
      } else {
        line = command.join(' ').trim(); }}Copy the code
  2. Type the alias

    Type aliases give a type a new name. Type aliases sometimes look like interfaces, but can work with primitive values, union types, tuples, and any other type you need to write by hand.

    
    type some = boolean | string
    
    const b: some = true // ok
    const c: some = 'hello' // ok
    const d: some = 123 // Cannot assign type '123' to type 'some'
    
    // Reference yourself
    type Tree<T> = {
        value: T;
        left: Tree<T>;
        right: Tree<T>;
    }
    
    Copy the code

    A type alias looks a lot like an interface, so how do you distinguish between the two? Interface can only be used to define object types, while type declaration can also define cross, union, primitive types, etc. Type declaration is obviously more widely applicable.

    But interfaces also have specific uses:

    • The interface mode implements the extends and implements of interfaces
    • Interface can implement interface merge declaration
  3. Identifiable union types

    Suppose a scenario, now two functions, one is to add users, namely add, and one is to remove users, namely remove.

    • Has common singleton type properties-recognizable characteristics,(in the following example, active has unique string literals)
    • A type alias contains union types
    • Features of type guards (e.g., if switch must be used to determine which type scope opt.action belongs to, i.e. delete and create)

    Example:

        
        inteface conf1  {
            action:'add'.payload:'xiaomuzhu'
        }
        inteface conf2  {
            action:'remove'.payload:'xiaomuzhu'
        }
        type params =  conf1 | conf2
        
        function fetch(opt:params){
            switch (opt.action) {
            case 'add':
                console.log(opt.payload);
                break;
            default:
                break; }}Copy the code

TypeScript progression

The advanced content in typeScript refers to a set of advanced types, including exposure to the usage and implementation of some of ts’s typing tools

pick

Looking at a scenario, we now need a pick function that pulls the specified properties from the object

In JavaScript this function would look like this:

function pick(o, names) {
  return names.map(n= > o[n]);
}

// Use of the pick method
pick({name:'a'.age:'88'},'name'.'age'])

Copy the code

How do you implement these value functions in TypeScript? Maybe

interface Obj {
    [key: string]: any
}
function pick(o: Obj, names: string[]) {
    return names.map(n => o[n]);
}
Copy the code

It seems easy to write, but you’ll find that the type definition is not rigorous enough:

  • The members of the names argument should be attributes of the o argument, so instead of a broad definition like string, it should be more accurate
  • The return type of the pick function is any[], which can be more precise. The return type of the pick function should be the combination of the attribute value types

How do you define a type more precisely? There are two type operators that you must understand: the index type query operator and the index access operator.

The index type

  1. Index Type query operator (KEYof)

    Keyof is the index type query operator. You can use keyof on the generic T to get all the public attribute names on the generic T to form the joint type. For example, we have an Images class that contains both SRC and Alt public attributes and uses keyof to name the attributes:

    class Images {
        public src: string = 'https://www.google.com.hk/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'
        public alt: string = 'Google'
        public width: number = 500
    }
    
    type propsNames = keyof Images
    
    // Type propsNames looks like this
    //propsNames = src | alt | width
    Copy the code
  2. Index access operator

    Keyof can be used to query the attribute name of an index type. How to obtain the attribute value type corresponding to the attribute name? This is where index accessors come in. Similar to JavaScript types that access property values, operators of access types are accessed through [], that is, T[K].

    Class Images' {public SRC: string = 'https://www.google.com.hk' public Alt: string = 'Google' public width: Number = 500} type propsNames = keyof Images Make sure that propsType must be the joint type of all keys under Images. Type propsType = Images[propsNames]Copy the code

    The type of the corresponding attribute value can be obtained through the type accessor T[K]. In keyof limiting K to the joint type of T generic key, the retrieved data T[K][] is exactly the type of the returned value

    The final implementation of pick function is as follows:

    function pick<T, K extends keyof T>(o: T, names: K[]): T[K][] {
        return names.map(n => o[n]);
    }
    
    const res = pick(user, ['token', 'id', ])
    
    Copy the code

Partial implementation

For example, if you have a User interface, there is a requirement to make all members of the User interface optional. What should you do? Shall I add them one by one? Is there a more convenient way? Ts provides the ability to map a type to a new type, let’s say a mapping type, its syntax is [K in Keys],

interface User { username: string id: number token: string avatar: string role: // Optional type partial<T> = {[K in keyof T]? : T[K]} // Read-only type readonly<T> = {readonly[K in keyof T]: T[K]}Copy the code

Partial interpretation

  • K

A type variable, bound in turn to each property, corresponds to the type of each property name

  • Keys

A combined type of string literals representing a set of attribute names.

Implementation steps:

  1. We first find Keys, the union type of string literals. Assuming that the type passed in is generic T, we get keyof T, the union type of the attribute name of the type passed in.
  2. Then we need to map the property names of keyof T one by one [K in keyof T],
  3. If you want to change all attribute members to optional types, you need T[K] to fetch the corresponding attribute value
  4. Finally, regenerate an optional new type {[K in keyof T]? : T [K]}.

Conditions in the

Sometimes you don’t have to write code to determine the type, but what type does it have to be

If T can be assigned to U, then the type is X. If T can be assigned to U, then the type is Y. F = T extends U? X : YCopy the code

Using the type tool Diff<T, U> as an example, find out what part of the T type U does not contain

B / / joint type "a" | "" | |" c "" d" and "a" | "c" | "f", which does not include the "b" | "d" type R = Diff < "a" | "b" | | "c" "d", "a" | "c" | "f" >; / / "b" | "d" / / similar to js array filter type, filter extends < T, U > = T U? T : never; type R1 = Filter<string | number | (() => void), Function>; / / rejecting null, and undefined type NonNullable < T > = Diff < T, null | undefined >;Copy the code

Think about it, I have an interfacePart, and now I need to write a utility type to pull out the name of the function type in interface

```js interface Part { id: number; name: string; subparts: Part[]; updatePart(newName: string): void; } type R = FunctionPropertyNames<Part>; // type R = "updatePart" ```Copy the code

In TypeScript type programming, we iterate over the interface, retrieve the Function, and retrieve the key. We iterate over the interface, retrieve the Function and retrieve the key.

type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]
Copy the code

Take apart the implementation of the above code in turn

  1. Assuming that Part is substituted into the generic T,[K in keyof T] is equivalent to traversing the entire interface
  2. K is the key of the interface, and T[K] is the value of the interface
  3. Next, verify the type of value with the conditional type, keeping value as the key of the new interface if it is Function, otherwise never
  4. Here we get the new interface after traversal:
  5. [keyof T] is used as the keyof the old interface Part
  6. But since the value of ID name and subparts is never, no type is returned, so only ‘updatePart’ is returned.

The never type means that there will be no value, that is, nothing, okay