TS
在 TypeScript 的官方文档和许多示例中,T
通常用于表示 "Type"
,K 用于表示 "Key"
,P 用于表示 "Property"
。
any&unknown&never
- any 可以表示任何类型的类型,会关闭 TypeScript 的类型检查。
- unknown 表示未知类型的类型,比any更安全,因为你不能直接对 unknown 类型的值进行操作,必须先进行类型检查或类型断言。
- never:这是一个表示永远不会发生的值的类型。
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)
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
let str = notSure.length; // okay
泛型
在定义函数、接口或者类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性。
function identity<T>(arg: T): T {
return arg;
}
const result1 = identity<number>(42); // Explicitly specifying the type
const result2 = identity('hello'); // Inferring the type
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):
使用联合类型|
type StringOrNumber = string | number;
let a: StringOrNumber;
a = 'Hello'; // OK
a = 123; // OK
交集(Intersection):
在数学中,交集是两个或更多集合共有的元素组成的集合。在 TypeScript 中,交集类型(Intersection)表示一个值同时具有几种类型的所有成员。
使用交叉类型&
type Named = { name: string };
type Aged = { age: number };
type NamedAndAged = Named & Aged;
let b: NamedAndAged;
b = { name: 'Alice', age: 23 }; // OK
差集(Difference):
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
装饰器(java里叫注解)
装饰器的本质就是一个函数,根据装饰器修饰的值的不同,将装饰器进行了分类。修饰什么类型的值就就是什么装饰器。
- 类装饰器
- 属性装饰器
- 方法装饰器
- 参数装饰器
// 1. 配置一下tsconfig.json
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
类装饰器
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();
完整的:
// 类装饰器
// 自动为对象每个参数设置一个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);
}
}
参数装饰器
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 关键字在类型推断中使用的。它主要在条件类型中使用,用于在类型关系检查中推断出一个类型。
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 编译器就会将该参数的类型缩小到指定的类型。
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 用于创建一个类(子类),该类继承另一个类(父类)的属性和方法。例如:
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 用于表示一个类型应该是另一个类型的子类型。例如:
function printLength<T extends { length: number }>(arg: T): void {
console.log(arg.length);
}
printLength("Hello, world!"); // Outputs: 13
分布式条件类型(Distributive Conditional Types)是一种特殊的条件类型,它在泛型类型推断时按照联合类型进行分布。这意味着它会将条件类型应用于联合类型的每个成员,并将结果组合成一个新的联合类型。
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
- 遍历对象的键:
let obj = { a: 1, b: 2 };
for (let key in obj) {
console.log(key); // 输出 "a" 和 "b"
}
- 类型保护(Type Guards):
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 的类型。
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
手动实现:
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 中的属性。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
手动实现:
type IOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P]
}
Pick<T, K>
从一个已有的类型T中挑选出一些子属性K,创建一个新的类型。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
手动实现:
type IPick<T, K extends keyof T> = {
[P in K]: T[P]
}