TypeScript

2022-12-27  本文已影响0人  bowen_wu

概述

类型擦除

TS 转化成 JS

方法

  1. npm i -g esbuild => esbuild 1.ts > 1.js => 快(不检查 TS 语法)
  2. npm i -g @swc/cli @swc/core => swc 1.ts -o 1.js => 快(不检查 TS 语法)
  3. npm i -g typescript => tsc 1.ts

数据类型

TS 描述对象

  1. 使用 class/constructor 描述
  2. 使用 type/interface 描述
type A = {
    // 索引签名
    [key: string]: number
}

// Record 泛型
type B = Record<string, number>;

const n: BigInt = 100n;
console.log(n); // 100n
const symbol: Symbol = Symbol("s");
console.log(symbol); // Symbol(s)

const date: Date = new Date()
const regexp: RegExp = /ab+c/;
const regexp1: RegExp = new RegExp("/\d+/");
const map: Map<string, number> = new Map();
map.set("a", 1);
const set: Set<number> = new Set();
set.add(1);

TS 描述数组

  1. type A = string[] == type A = Array<string>
  2. 元组 => type A = [string, string, number] => length == 3
  3. type A = [1, 2, 3 | 4] => const a: A = [1, 2, 3]

TS 描述函数

  1. type FnA = (a: number, b: number) => string => const a: FnA = () => "hello world";
type FnReturnVoid = () => void;
type FnReturnUndefined = () => undefined;

const f1: FnReturnVoid = () => {
    console.log("hi");
}

const f2: FnReturnUndefined = () => {
    console.log("hi");

    // 此处需要显式声明
    return undefined;
}

type Person = {
    name: string,
    age: number,
    sayHi: FnWithThis
}
type FnWithThis = (this: Person, name: string) => void;

// 此处如果使用箭头函数,则 this 指向有问题
const sayHi: FnWithThis = function () {
    console.log("hi " + this.name)
}

const ming: Person = {
    name: "ming",
    age: 18,
    sayHi: sayHi
}

ming.sayHi("Jack");
sayHi.call(ming, "Jack");

any vs unknown

never

enum

  1. 使用一个有意义的名字代表一个状态
enum A {
    TODO = 0,
    DONE,
    ARCHIVED,
    DELETED
}

// 类型擦除时 A.DONE 会变成 1
let status: A = A.DONE;

  1. 通过使用位运算可以很方便的将权限进行组合和对比
enum Permission {
    NONE = 0, // 0000
    READ = 1 << 0, // 0001
    WRITE = 1 << 1, // 0010
    DELETE = 1 << 2, // 0100
    MANAGE = READ | WRITE | DELETE // 0111
}

type User = {
    permission: Permission
}

const user: User = {
    permission: 0b0011
}

if ((user.permission & Permission.WRITE) === Permission.WRITE) {
    console.log("拥有写权限")
}
  1. 使用 enum 做字符串映射字符串时可以使用类型别名代替
// enum Status {
//     done = 'done',
//     process = 'process',
//     start = 'start'
// }

type Status = 'done' | 'process' | 'start';

type

type A = { name: string };
const a1 = { name: 'ming', age: 18 };

// 此时 TS 不会做严格检查
const a2: A = a1;

// Error => TS 第一次声明的时候会进行严格检查
const a: A = { name: 'ming', age: 18 }

// 其中的 0、null、undefined、''、false 都是类型
type Falsy = 0 | null | undefined | '' | false;

// 类型A是一个集合,集合中只有一个0
type A = 0;

type FnWithProp = {
    (a: number, b: number): number,
    props1: number;
}

const fn: FnWithProp = (x, y) => x + y;
fn.props1 = 101;

console.log(fn);
console.log(fn.props1)

interface

type vs interface

  1. interface 只描述对象,type 则描述所有类型
  2. interface 是类型声明,type 是别名
    type A = string;
    type B = A; // B is string
    
    interface C extends Date {
    }
    
    type D = C; // D is C
    
  3. interface 可以进行 merging(扩展),type 不可以重新赋值 => 对外 API 尽量使用 interface,方便扩展。对内 API 尽量使用 type(case: props),防止代码分散
  4. interface extends 时如果属性冲突,直接报错,type 会联合类型到 never

Object vs object

void vs null vs undefined

let a: void

// 此时 void = null | undefined
if (typeof a === 'object') {
    // a is null
    console.log(a);
} else if (typeof a === 'undefined') {
    // a is undefined 
    console.log(a);
} else {
    // a is never 
    console.log(a);
}

// 此时 void = undefined
if (typeof a === 'undefined') {
    // a is undefined
    console.log(a);
} else if (typeof a === 'object') {
    // a is never 
    console.log(a);
} else {
    // a is never 
    console.log(a);
}

Assignment

Assignment

TS 类型系统运算

联合类型

JS 语言特性区分类型

const fn = (a: number | string) => {
    // 类型收窄 => Narrowing
    if (typeof a === 'number') {
        a.toFixed(2);
    } else if (typeof a === 'string') {
        a.split(',');
    } else {
        // a === never
        console.log(a);
    }
}

type Person = {
    name: string;
}

type Animal = {
    x: string;
}

const f1 = (param: Person | Animal) => {
    // 使用 in 进行类型收窄
    if ('name' in param) {
        console.log(param.name);
    } else {
        console.log(param.x);
    }
}

类型谓词 is

type Rect = {
    height: number,
    width: number
}

type Circle = {
    center: [ number, number ],
    radius: number
}

const fn = (a: Rect | Circle) => {
    if (isRect(a)) {
        // a is Rect
        console.log(a.height);
    } else if (isCircle(a)) {
        // a is Circle
        console.log(a.radius);
    } else {
        // a is never
        console.log(a);
    }
}

function isRect(x: Rect | Circle): x is Rect {
    return 'height' in x && 'width' in x;
}

// 此处 x is Circle 不能写在左侧 => const isCircle: (x: Rect | Circle) => x is Circle = x => 'radius' in x && 'center' in x;
const isCircle = (x: Rect | Circle): x is Circle => {
    return 'radius' in x && 'center' in x;
}

可辨别联合 Discriminated Unions

type Circle = { kind: "Circle", center: [ number, number ] };
type Square = { kind: "Square", sideLength: number };
type Shape = Circle | Square;

const f1 = (a: string | Shape) => {
    if (typeof a === 'string') {
        // a is string
        a.split(',');
    } else if (a.kind === 'Circle') {
        // a is Circle
        console.log(a.center);
    } else {
        // a is Square
        console.log(a.sideLength);
    }
}

断言 as

// a is string
let a = 'hi';

// b is hi
const b = 'hi';

// c is hi
let c = 'hi' as const;

// array is (number | string)[] => 对于引用类型在 TS 中 const 相当于 let
const array = [ 1, 'hi' ];

// array1 is readonly [1, 'hi']
const array1 = [ 1, 'hi' ];

// Error: Property 'push' does not exist on type 'readonly [1, "hi"]'.
array1.push(2);

交叉类型 intersection types

type

type Left = {
    left: string;
}

type Right = {
    right: string;
}

type C = Left | Right;
type D = Left & Right;
const c: C = {
    left: "left"
}
const d: D = {
    left: 'left',
    right: 'right'
}

type Person = {
    name: string;
    age: number;
    id: string
}

// 此处 id 属性的类型是 string & number => id is never
type User = Person & {
    id: number;
    email: string;
}

const user: User = {
    id: 1 as never,
    name: 'ming',
    age: 18,
    email: 'email'
}

// userIsNever is never
type UserIsNever = { id: 'A' } & { id: 'B' };

interface

interface Colorful {
    color: string;
}

interface Circle {
    radius: number
}

type ColorfulCircle = Colorful & Circle;

interface Person {
    id: string;
    name: string;
}

interface User extends Person {
    // Error: type 'number' is not assignable to type 'string'.
    // id: number;
    email: string;
}

神奇

type A = {
    method: (n: number) => void;
}

type B = {
    method: (n: string) => void;
} & A;
const b: B = {
    // n is any
    method: (n) => {
        console.log(n);
    }
}

type F1 = (n: number) => void;
type F2 = (n: string) => void;
type X = F1 & F2;
const x: X = (n) => {
    // n is any
    console.log(n);
    n.split(',')
    n.toFixed(2);
}

类型兼容 & 赋值

基本类型

type A = string | number;
const a: A = 'hi';

普通对象

type Person = {
    name: string;
    age: number;
}

let user = { name: 'ming', age: 18, id: 1 };
let p: Person;
p = user;
// type User = {name: string; age: number; id: nubmer}
type User = typeof user;

接口

interface Parent {
    x: string;
}

interface Sub extends Parent {
    y: string;
}

let sub: Sub = {
    x: '1',
    y: '2'
};

let p: Parent;
p = sub;

函数

深入对象语法

索引签名 Index Signature

type Hash = {
    [k: string]: unknown;
    length: number;
}

映射类型 Mapped Type

type Hash = {
    // 不能再写其他属性
    [key in string]: unknown;

    // length: number; => error
}

只读属性 readonly

interface User {
    readonly id: number;
    age?: number;
}

const user: User = {
    id: 1,
    age: 18,
}
// Error: Cannot assign to 'id' because it is a read-only property
user.id = 2;

深入函数语法

对象语法全部适用于函数

type F = {
    (a: number, b: number): number;
    readonly count?: number;
}

type F1 = (a: number, b: number) => number;

const f: F = (x, y) => x + y;

声明函数及其类型

// First
type F1 = (a: number, b: number) => number;
const f1: F1 = (a, b) => a + b;

// Second
const f2 = (a: number, b: number): number => a + b;
type F2 = typeof f2;

// Third
function f3(this: unknown, a: number, b: number): number {
    return a + b;
}

type F3 = typeof f3;

// Fourth
const f4 = function (this: unknown, a: number, b: number): number {
    return a + b;
}
type F4 = typeof f4;

类型谓词

可选参数

参数默认值

function addEventListener(eventType: string, fn: (this: HTMLElement, e: Event) => void, useCapture = false) {
    const element = {} as HTMLElement;
    const event = {} as Event;
    fn.call(element, event);
}

addEventListener('click', () => console.log(1));

重载 overload

function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(a: number, b?: number, c?: number): Date {
    if (a !== undefined && b !== undefined && c !== undefined) {
        return new Date(a, b, c);
    } else if (a !== undefined && b === undefined && c === undefined) {
        return new Date(a);
    } else {
        throw new Error('Params is invalid!');
    }
}

// Error: No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
createDate(2022, 10);

展开参数

function sum(...array: number[]) {
    // Error: Argument of type 'number[]' is not assignable to parameter of type 'number'.
    // f(array)
    f(...array);
    f.apply(null, array);
}

function f(...array: number[]) {
    console.log(array);
}

as const

function add(a: number, b: number): number {
    return a + b;
}

// arr is number[]
const arr = [ 1, 2 ];

// Error: A spread argument must either have a tuple type or be passed to a rest parameter.
add(...arr);

// arr1 is readonly [1, 2]
const arr1 = [ 1, 2 ] as const;
add(...arr1);

解构

type Config = {
    url: string;
    method: "GET" | "POST" | "PUT" | "DELETE";
    body?: unknown;
    headers?: unknown
}

// rest is {body?: unknown; headers?: unknown}
function ajax({ url, method, ...rest }: Config = { url: '', method: "GET" }) {
}

function ajax1({ url, method, ...rest } = { url: '', method: "GET" } as Config) {
}

泛型 Generic Type

type F<A, B> = A | B;

// Result is number | string
type Result = F<number, string>;

function echo(msg: number | boolean | string) {
    return msg;
}

// F is (msg: number | boolean | string) => number | boolean | string;
type F = typeof echo;

类型参数默认值

interface Hash<V = string> {
    [k in string]: V;
}

条件类型 Conditional Type

type LikeString<T> = T extends string ? true : false;

type R1 = LikeString<'hi'>; // true
type R2 = LikeString<true>; // false
type X = LikeString<never>; // never
type Y = LikeString<string | number>; // Y is boolean

type ToArray<T> = T extends unknown ? T[] : never;
type Result = ToArray<number | string>; // Result is number[] | string[]
type Result1 = ToArray<never>; // Result1 is never

type Z = never extends unknown ? 1 : 2; // Z is 1

// 此处不能对应 string -> string[]  number -> number[] 
type U = string | number extends unknown ? string[] | number[] : 1;

keyof

type Person = { name: string, age: number };
type GetKeys<T> = keyof T;
type Result = GetKeys<Person>; // Result is name | age

泛型约束

type GetKeyType<T, K extends keyof T> = T[K];
type Result1 = GetKeyType<Person, "name">; // string
type Test = Person['name']; // Test is string

内置

Readonly

type Person = { name: string, age: number };
// type Result = {
//   readonly name: string;
//   readonly age: number;
// }
type Result = Readonly<Person>;
// type Readonly<T> = {
//     readonly [k in keyof T]: T[k];
// }

Partial

type Person = { name: string, age: number };
// type Result = {
//     name?: string | undefined;
//     age?: number | undefined;
// }
type Result = Partial<Person>;
// type Partial<T> = {
//   [k in keyof T]?: T[k];
// }

Required

type Person = { name: string, age: number, email?: string };
// type Result = {
//   name: string;
//   age: number;
//   email: string;
// }
type Result = Required<Person>;
// type Required<T> = {
//   [k in keyof T]-?: T[k];
// }

Record

type Result = Record<string, number>;
// type Record<K extends string | number | symbol, T> = {
//   [k in K]: T;
// }

Exclude

type Result = Exclude<1 | 2 | 3, 1 | 2>; // Result is 3
// type Exclude<T, K> = T extends K ? never : T;

Extract

type Result = Extract<1 | 2 | 4, 2 | 3>; // Result is 3
// type Extract<T, K> = T extends K ? T : never;

Pick

type Person = { name: string, age: number, email?: string };
// type Result = {
//   name: string | number;
//   age: string | number;
// }
type Result = Pick<Person, 'name' | 'age'>;
// type Pick<T, K extends keyof T> = {
//   [key in K]: T[K];
// }

Omit

type Person = { name: string, age: number, email?: string };
// type Result = {
//     email?: string | undefined;
// }
type Result = Omit<Person, 'name' | 'age'>;
// type Omit<T, K> = {
//     [k in keyof T as (k extends K ? never : k)]: T;
// }

// 使用 Pick
// type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

-readonly

type Person = { name: string, age: number, email?: string };
// type Result = {
//   name: Readonly<Person>;
//   age: Readonly<Person>;
//   email?: Readonly<Person> | undefined;
// }
type Result = Mutable<Readonly<Person>>;
type Mutable<T> = {
    -readonly [key in keyof T]: T;
}

class

class Point {
    x: number;
    y: number;

    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
}

// 等价于
class Point1 {
    constructor(public x = 0, public y = 0) {

    }
}

class Hash {
    [key: string]: unknown;

    set(key: string, value: unknown) {
        this[key] = value;
    }

    get(key: string) {
        return this[key];
    }
}

constructor reload

class Point {
    x!: number;
    y!: number;

    constructor(x: number, y: number);
    constructor(str: string);
    constructor(xs: number | string, y?: number) {
        if (typeof xs === 'number' && typeof y == 'number') {
            this.x = xs;
            this.y = y;
        } else if (typeof xs === 'string') {
            this.x = parseFloat(xs.split(',')[0]);
            this.x = parseFloat(xs.split(',')[1]);
        }
    }
}

implements

interface Person {
    name: string;
    sayHi: (target: Person) => void;
}

interface Taggable {
    tags: string[];
    addTag: (tag: string) => void;
    removeTag: (tag: string) => void;
}

class User implements Person, Taggable {
    constructor(public name: string, public tags: string[] = []) {
    }

    sayHi(target: Person) {
        console.log('Hi ' + target.name);
    }

    addTag(tag: string) {
        this.tags.push(tag);
    }

    removeTag(tag: string) {
        this.tags.splice(this.tags.indexOf(tag), 1);
    }
}

const ming = new User('ming');
ming.sayHi(ming);

declare

class Person {
    friend?: Person;

    constructor(public name: string, friend?: Person) {
        this.friend = friend;
    }
}

class User extends Person {
    // Error: Property 'friend' will overwrite the base property in 'Person'. If this is intentional, add an initializer. Otherwise, add a 'declare' modifier or remove the redundant declaration.
    // friend?: User;
    declare friend?: User;

    constructor(public id: string, name: string, friend?: User) {
        super(name, friend);
    }
}

const u1 = new User("1", "jack");

const u2 = new User("2", "george", u1);

// 如果没有 declare => Friend is Person | undefined
// 如果有 declare => Friend is User | undefined
type Friend = typeof u2.friend;

public & private & protected

static

class Foo {
    static #count = 0;
    static {
        const count = loadFromLocalStorage() || 0;
        Foo.#count += count;
    }

    constructor() {
        console.log(Foo.#count);
    }
}

抽象类

abstract class A {
    abstract name: string;
    age: string;

    constructor(age: string) {
        this.age = age;
    }

    abstract getName(): string;

    printName() {
        console.log("Hi, " + this.getName());
    }
}

类作为参数

class Person {
    constructor(public name: string) {
        this.name = name;
    }
}

function f(X: typeof Person) {
    const p = new X('George');
}

f(Person)

// new 表示传入的参数必须是 class
function f2(X: new (name: string) => Person) {
    const p = new X("George");
}

f2(Person);

类型体操

// if (A <= B) true else false
type A = 1;
type B = 1 | 2;

type Result = A extends B ? true : false;
// 空元组
type A = [];
type IsEmptyArray<Arr extends unknown[]> = Arr['length'] extends 0 ? true : false;

type Result = IsEmptyArray<[]>;

// 非空元组
type B = [ 1 ];
type NotEmpty<Arr extends unknown[]> = Arr extends [ ...unknown[], unknown ]
    ? true
    : false;
type NotEmpty1<Arr extends unknown[]> = Arr extends [ ...infer X, infer Last ]
    ? true
    : false;

type Result1 = NotEmpty<B>;

递归

type A = [ 1, 2, 3, 4 ];
type Reverse<Arr extends unknown[]> = Arr extends [ ...infer Rest, infer Last ] ? [ Last, ...Reverse<Rest> ] : Arr;

type Result = Reverse<A>;

模式匹配 + infer

type A = [ 1, 2, 3, 4 ];

// Result1 is 1
type Result1 = A extends [ infer First, ...infer Rest ] ? First : never;

// Result2 is [2, 3, 4]
type Result2 = A extends [ string, ...infer Rest ] ? Rest : never;

元组体操

// 将元组加长
type A = [ 1 ];
type B = [ ...A, number ];
type C = [ ...B, 'hi' ];
type D = [ ...B, ...C ];

// get Last type in Tuple
type Last<T extends unknown[]> = T extends [ ...unknown[], infer Last ] ? Last : never;
type Hi = Last<C>; // Hi is 'hi'

// Error => TS 没有减法
// type Last<T extends unknown[]> = T[T["length"] - 1];



字符串体操

Capitalize & Uncapitalize & Uppercase & Lowercase

type A = "george";
type B = Capitalize<A>; // B is George

type C = 'hi' | 'george';
type D = Capitalize<C>; // D is 'Hi' | 'George'

模板字符串

type A = "Hi";
type B = "George";

type Result = `${A} ${B}`;

模式匹配

type A = "Hi George";
type First<T extends string> = T extends `${infer F}${string}` ? F : never;
type A = "Hi George";

type LastOfTuple<T extends unknown[]> = T extends [ ...infer _, infer L ] ? L : never;

// String to Tuple
type StringToTuple<S extends string> = S extends `${infer F}${infer Rest}` ? [ F, ...StringToTuple<Rest> ] : [];
type Last<T extends string> = LastOfTuple<StringToTuple<T>>;

type Result = First<A>; // Result is H
type ResultOfLast = Last<A>; // Result is e

String to Union

type A = "Hi George";
type StringToUnion<S extends string> = S extends `${infer First}${infer Rest}` ? First | StringToUnion<Rest> : never;
type Result = StringToUnion<A>;

TSX

知识点

  1. 在 TSX 中 input 的 onChange 实际上调用的是 oninput 事件
    • onchange => 触发时机是失去焦点时
    • oninput => 触发时机是输入时
上一篇下一篇

猜你喜欢

热点阅读