Skip to content

TS

在 TypeScript 的官方文档和许多示例中,T 通常用于表示 "Type",K 用于表示 "Key",P 用于表示 "Property"

any&unknown&never

  • any 可以表示任何类型的类型,会关闭 TypeScript 的类型检查。
  • unknown 表示未知类型的类型,比any更安全,因为你不能直接对 unknown 类型的值进行操作,必须先进行类型检查或类型断言。
  • never:这是一个表示永远不会发生的值的类型。
ts
let notSure: unknown = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

let str = notSure.length; // Property 'length' does not exist on type 'unknown'.ts(2339)
ts
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

let str = notSure.length; // okay

泛型

在定义函数、接口或者类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性。

ts
function identity<T>(arg: T): T {
  return arg;
}
const result1 = identity<number>(42); // Explicitly specifying the type
const result2 = identity('hello'); // Inferring the type
ts
type IFunc = <T extends Object, K extends keyof T>(obj: T, key: K) => T[K]

interface IFunc {
  <T extends Object, K extends keyof T>(obj: T, key: K): T[K]
}
const getValue: IFunc = (obj, key) => {
    return obj[key];
};

getValue({ a: 1 }, 'a');
getValue({ a: 1 }, 'b'); // error
getValue('1', 'b'); // error

交集&并集&差集

并集(Union):

使用联合类型|

ts
type StringOrNumber = string | number;

let a: StringOrNumber;
a = 'Hello'; // OK
a = 123; // OK

交集(Intersection):

在数学中,交集是两个或更多集合共有的元素组成的集合。在 TypeScript 中,交集类型(Intersection)表示一个值同时具有几种类型的所有成员。

使用交叉类型&

ts
type Named = { name: string };
type Aged = { age: number };
type NamedAndAged = Named & Aged;

let b: NamedAndAged;
b = { name: 'Alice', age: 23 }; // OK

差集(Difference):

ts
type T0 = Exclude<"a" | "b" | "c", "a">;  // "b" | "c"

装饰器(java里叫注解)

装饰器的本质就是一个函数,根据装饰器修饰的值的不同,将装饰器进行了分类。修饰什么类型的值就就是什么装饰器。

  • 类装饰器
  • 属性装饰器
  • 方法装饰器
  • 参数装饰器
json
// 1. 配置一下tsconfig.json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

类装饰器

ts
const MessageDecorator: ClassDecorator = (target: Function) => {
  target.prototype.message = (context: string) => {
    console.log(context);
  }
}

@MessageDecorator
class LoginController {
    public login () {
        console.log('登入业务处理');
        console.log('登入成功消息');
        (this as any).message('恭喜登入成功'); 
    }
}
new LoginController().login();

完整的:

ts
// 类装饰器
// 自动为对象每个参数设置一个getter\setter方法
function Data(constructor: Function | ObjectConstructor) {
  console.log('@Data', { constructor })
  Object.getOwnPropertyNames(new constructor()).forEach(key => {
    const firstUpperProperty = key.charAt(0).toUpperCase() + key.substring(1)
    if (!constructor.prototype[`get${firstUpperProperty}`]) {
      constructor.prototype[`get${firstUpperProperty}`] = function () {
        return this[key]
      }
    }
    if (!constructor.prototype[`set${firstUpperProperty}`]) {
      constructor.prototype[`set${firstUpperProperty}`] = function (value: any) {
        this[key] = value
      }
    }
  })
}

// 属性装饰器
// 为属性的getter设置默认值
function DefaultVal(val: any) {
  return function (target: any, key: string) {
    console.log('@DefaultVal', { target, key, val });
    const firstUpperProperty = key.charAt(0).toUpperCase() + key.substring(1)
    target.constructor.prototype[`get${firstUpperProperty}`] = function () {
      return this[key] ?? val
    }
  };
}

// 方法装饰器
// 指定返回值不能为null | undefined,否则报错
function NonNull() {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('@NonNull', { target, propertyKey, descriptor });
    let value = descriptor.value
    descriptor.value = () => {
      const result = value.call()
      if (result === null || result === undefined) throw new Error(`${propertyKey} must non-null`)
      return result
    }
  };
}

// 参数装饰器
// console.log
function Log(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  console.log('@Log', { target, propertyKey, parameterIndex });
}

@Data
class User {
  private id: Number | undefined;
  @DefaultVal('nobody')
  private name: string | undefined;
  @NonNull()
  toString() {
    return Object.prototype.toString();
  }
  equals(@Log val: User) {
    return deepEqual(this, val);
  }
}

参数装饰器

ts
function logParameter(target: any, key: string, index: number) {
  console.log(target, key, index)
}

class MyClass {
  myMethod(@logParameter arg1: string, @logParameter arg2: number) {
    console.log(`Hello, ${arg1}, ${arg2}`);
  }
}

const myClass = new MyClass();
myClass.myMethod('world', 42);

类型体操

infer

infer 关键字在类型推断中使用的。它主要在条件类型中使用,用于在类型关系检查中推断出一个类型。

ts
type IReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never

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

type AddReturnType = IReturnType<typeof addFn>

is

在 TypeScript 中,is 关键字用于用户自定义的类型保护。

通常用于函数,这些函数在运行时检查参数是否为特定类型,如果检查通过,TypeScript 编译器就会将该参数的类型缩小到指定的类型。

ts
function isString(test: any): test is string {
  return typeof test === "string";
}

function example(foo: any) {
  if (isString(foo)) {
    console.log("It is a string: " + foo);
    console.log(foo.length); // string function
  }
}

extends

  • 类继承:在面向对象编程中,extends 用于创建一个类(子类),该类继承另一个类(父类)的属性和方法。例如:
ts
class Animal {
  move() {
    console.log("Moving...");
  }
}

class Dog extends Animal {
  bark() {
    console.log("Barking...");
  }
}

const dog = new Dog();
dog.move(); // Outputs: "Moving..."
dog.bark(); // Outputs: "Barking..."
  • 类型约束:在泛型和条件类型中,extends 用于表示一个类型应该是另一个类型的子类型。例如:
ts
function printLength<T extends { length: number }>(arg: T): void {
  console.log(arg.length);
}

printLength("Hello, world!"); // Outputs: 13

分布式条件类型(Distributive Conditional Types)是一种特殊的条件类型,它在泛型类型推断时按照联合类型进行分布。这意味着它会将条件类型应用于联合类型的每个成员,并将结果组合成一个新的联合类型。

ts
type ToArray<T> = T extends any ? T[] : never;

type Example = ToArray<number | string>;
// 解析为:type Example = number[] | string[]

在上面的例子中,我们定义了一个条件类型ToArray<T>,它接受一个类型参数T。该条件类型的逻辑是,如果T是任意类型(T extends any),则返回T的数组类型(T[]),否则返回never。然后,我们使用ToArray条件类型将联合类型number | string转换为数组类型。

这里的关键是,分布式条件类型会将联合类型的每个成员应用于条件类型。在我们的例子中,ToArray<number | string>将被分解为ToArray<number> | ToArray<string>,然后根据条件类型的逻辑,分别转换为number[]和string[]。最后,结果会合并为number[] | string[]。

in

  • 遍历对象的键:
ts
let obj = { a: 1, b: 2 };
for (let key in obj) {
  console.log(key); // 输出 "a" 和 "b"
}
  • 类型保护(Type Guards):
ts
type A = { a: number };
type B = { b: string };

function doSomething(x: A | B) {
  if ('a' in x) {
    console.log(x.a); // x 被 TypeScript 确定为类型 A
  } else {
    console.log(x.b); // x 被 TypeScript 确定为类型 B
  }
}

Exclude<T, U>

从 T 可赋值的类型中排除 U。换句话说,它创建一个新的类型,该类型包含 T 中所有不可赋值给 U 的类型。

ts
type T0 = Exclude<"a" | "b" | "c", "a">;  // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;  // "c"

手动实现:

ts
type IExclude<T, U> = T extends U ? never : U

/**
  当 T extends U 中的 T 是一个联合类型时,TypeScript 会对 T 中的每个成员进行检查。这被称为 "分布式条件类型"。
  例如,如果 T 是 "a" | "b" | "c",那么 T extends U ? X : Y 会被展开为 ("a" extends U ? X : Y) | ("b" extends U ? X : Y) | ("c" extends U ? X : Y)。
  这就是为什么 IExclude<"a" | "b" | "c", "a"> 可以正确地排除 "a",并返回 "b" | "c" 的原因。
 * /

Omit<T, K>

从 T 中排除 K 中指定的属性。换句话说,它创建一个新的类型,该类型包含 T 中所有不在 K 中的属性。

ts
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Omit<Todo, "description">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

手动实现:

ts
type IOmit<T, K extends keyof T> = {
  [P in Exclude<keyof T, K>]: T[P]
}

Pick<T, K>

从一个已有的类型T中挑选出一些子属性K,创建一个新的类型。

ts
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, 'title' | 'completed'>;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

手动实现:

ts
type IPick<T, K extends keyof T> = {
  [P in K]: T[P]
}