# TypeScript冷门概念

# 基础概念

# unknown

# 用于断言

const num: number = 10;
(num as unknown as string).split(''); // 这里和any一样完全可以通过静态检查

# 用于类型收窄

使用any类型,相当于直接放弃类型检查

function test(input: unknown): number {
 if (Array.isArray(input)) {
   return input.length; // 通过: 这个代码块中,类型守卫已经将input识别为array类型
 }
  return input.length; // Error: 这里的input还是unknown类型,静态检测报错。如果入参用any,则会放弃检查直接成功,带来报错风险
}

# void与undefined

void不在意返回类型值

function invokeCallback(callback: () => void) {} // 声明callback的返回值为void

invokeCallback(() => 1); // 通过:这里的callback函数返回值是number类型
invokeCallback(() => 'foo'); // 通过:这里的callback函数返回值是string类型

undefined

function invokeCallback(callback: () => undefined) {} // callback的返回值为undefined

invokeCallback(() => 1); // Error:Type 'number' is not assignable to type 'undefined'
invokeCallback(() => 'foo'); // Error:Type 'string' is not assignable to type 'undefined'

# never

无法正常返回结束的类型

function foo(): never { throw new Error('error message') } // throw error 返回值是never
function foo(): never { while(true) } // 死循环无法退出

type human  = 'boy' & 'girl'; // 这两个类型不可能相交,因此human为never类型

// **********
// 任何类型联合never都会是原来的类型
// **********
type language = 'ts' | never; // language的类型还是'ts'类型


// **********
// never后的代码会变成Deadcode不再会执行
// **********
function bar() {
  foo(); // 这里的foo返回never
  console.log('Unreachable code'); // Errror: 编译器报错,此行代码永远不会执行到
}
                       
let foo: never;
let bar: any = {};
foo = bar; // Error: 不能把非never类型赋值给never类型,包括any

# 字符串模板

// **********
// 基础用法
// **********
type World = "world";
type Greeting = `hello ${World}`;
// type Greeting = "hello world";

// **********
// 联合类型
// **********
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id";


// **********
// 通过字符串拼接,限制foo.on的eventName参数值为firstNameChanged|lastNameChanged|ageChanged
// **********
type Listenable<T> = {
  on(
    eventName: `${string & keyof T}Changed`,
    callback: (newValue: any) => void,
  ): void;
}
declare function watch<T>(obj: T): T & Listenable<T>;
const foo = watch({
  firstName: "wilson",
  lastName: "zeng",
  age: 20,
});
foo.on("firstNameChanged", () => {
  console.log('firstNameChanged was changed');
});

# 高级概念

# 函数重载

// *******
// 基本定义
// *******
function foo(a: string): number; // 重载定义1
function foo(a: string, b: number): number; // 重载定义2

function foo(a: string, b?: number): number {
  // 具体实现
}

foo('bar'); // 应用重载规则1
foo('bar', 1); // 应用重载规则2


// **********
// 实际场景举例
// **********
interface User {
 name: string;
  age: number;
}

// 第一个参数可以传入用户或用户ID,但是当传入用户ID时,必须传入一个idChecker方法,强依赖,这时就需要通过函数重载来实现
function saveUser(userOrId: User | number, idChecker?: () => boolean): void {
  // ...
}

// 实现方式
function saveUser(user: User): User;
function saveUser(id: number, idChecker: () => boolean): number;
function saveUser(userOrId: User | number, idChecker?: () => boolean): User | number {
  // ...
}

saveUser({ name: '用户名', age: 20 }); // 通过:传入User
saveUser(30, () => true); // 通过:传入ID
saveUser(30); // Error: Argument of type 'number' is not assignable to parameter of type 'User'

# 接口

// ******
// 接口基本定义
// ******
// 接口
interface Point {
  x: number;
  y: number;
}
declare const myPoint: Point;

// 内联注解
declare const myPoint: { x: number; y: number };


// ******
// 描述函数结构
// *****
interface AddFunc {
 (a: number, b: number): number; 
}

const sum: AddFunc = function (a: number, b: number) {
  // ...
};

// ***********
// 描述类的实现
// ***********
interface BaseUser {
 name: string; 
}

class User implements BaseUser {
  // Error: Property 'name' is missing
  title: string = '';
  
  constructor() {
    return { title: 'hello' };
  }
}

// ************
// 描述构造函数
// ***********
// 通过new关键字描述构造函数
function createUser(ctor: { new(): { title: string } }) {
  return new ctor();
}

const user = createUser(User); // 返回对象: { title: 'hello' }

// *********
// 接口的开放性
// *********
// 不允许重名,但是在不同文件可以重复声明
// Lib a.d.ts
interface Point {
  x: number;
  y: number;
}
declare const myPoint: Point;

// Lib b.d.ts
interface Point {
  z: number;
}

// Your code
myPoint.z // Allowed!

// *********
// 接口继承
// ********
// 通过extends继承
interface MyPoint extends Point {
  name: string;
}

// *********
// 接口与类
// *********
// TS的类既可以作为类使用,也可以作为接口使用
class Point {
  x: number;
  y: number;
}
const p0 = new Point(); // 实例化Point
declare const p: Point; // 声明p为Point类型

// ********
// 接口继承类
// ********
class Point {
  x: number;
  y: number;
  constructor(x: number, y: number) {
    // ....
  }
}
// 之所以接口可以继承类,是因为TS的类会同时声明一个接口,因此实际上是接口继承接口
interface Point3D extends Point {
  z: number;
}


// ***********
// 接口与type
// ***********
interface Foo { name: string; } // 接口 - 接口是描述结构的类型
type Foo = { name: string; } // type - type是类型的别名
interface Foo { name: string; } === { name: string; } // 接口等同接口的字面量形式

interface Foo<T> { name: T; } // 接口 - 接口是描述结构的类型
type Foo<T> = { name: T; } // type - type是类型的别名
interface Foo<T>{ name: T; } === { name: T; } // 接口等同接口的字面量形式

# 泛型

// *********
// 理解泛型
// *********
function logKeyValueObj<K, V>(key: K, value: V) {
 console.log({
   [key]: value
 });
}

logKeyValueObj<string, boolean>('female', true);


// ************
// 对泛型进行约束
// ************
function sum<T>(value: T[]): number {
  let count = 0;
  value.forEach(v => count += v); // Error: T的类型和number不一定兼容
  return count;
}

// 基础场景
function sum<T extends number>(value: T[]): number {
  let count = 0;
  value.forEach(v => count += v); // 通过
  return count;
}

// 多个泛型参数
function pick<T, K extends keyof T>(source: T, names: K[]) {
 // ... 
}


// ***********
// 泛型条件(extends)
// ***********
// 语法:T extends U ? X : Y
type A = 'x' extends 'x' ? string : number; // string
type B = 'x' | 'y' extends 'x' ? string : number; // number

type Result<T> = T extends 'x' ? string : number;
type C = Result<'x' | 'y'> // C 和 B相同? 并不相同 type为string | number

// 原因如下,泛型传入联合类型时,存在默认的【分配行为】,会单独拆分
// 1. 先分解为单项
Result<'x' | 'y'> => Result<'x'> | Result<'y'>

// 2. 单独带入条件
// 'x'代入得到 string
'x' extends 'x' ? string : number => string
// 'y'代入得到 number
'y' extends 'x' ? string : number => number
// 3. 然后将每一项代入得到的结果联合起来,得到string | numer

// ***********
// 阻断分配行为
// ***********
// 通过中括号可以做到泛型分配行为的阻断效果
type Result<T> = [T] extends ['x'] ? string : number;
type C = Result<'x' | 'y'> // number

// ***********
// 泛型推断(infer)
// ********
type GetNameType<T> = T extends { name: infer N } ? N : never;
// 如何使用
type A = GetNameType<number> // never,因为number不是一个包含t的对象类型
type B = GetNameType<{ name: boolean }> // boolean,因为泛型参数匹配上了,使用了infer对应的type
type C = GetNameType<{ a: number, name: () => void }> // () => void,与我们要的结构兼容,所以使用infer对应type


// **********
// 一些实际例子
// **********
// 获取函数的返回值类型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type A = ReturnType<() => string>; // string

// 获取元组中第2个元素类型
type GetSecondElement<T> = T extends [any, infer E2, ...any] ? E2 : any;
type A = GetSecondElement<[string, number, boolean, symbol]>; // number,推断得到数组的第2个元素类型

// 分解字符串为键值对对象
type SplitKeyValue<T> = T extends `${infer K}=${infer V}` ? { [k in K]: V } : never;
type A = SplitKeyValue<'name=wilson'>; // { name: 'wilson' } 


// **********
// 泛型与递归
// **********
// TypeScript没有循环和判断的能力,但是提供了类型嵌套的能力,可以实现循环的效果
typeof RecursiveReadonly<T> = {
  readonly [key in keyof T]: RecursiveReadonly<T[key]>
};

# 装饰器

理念:在不改变对象自身的基础上,动态增加额外的职责

// *********
// 装饰器实现原理
// *********
// 传统装饰器实现方式
function myDecorator(
  target: any,
   prop?: string | number | symbol,
   descriptor?: PropertyDescriptor,
) {
  // 做任意修改操作
  Object.defineProperty(target, prop, {
    ...descriptor,
    // ...
  });
}

// TS中通过@符号来使用,一种是修饰类,一种是修饰类的成员
// 实际上是一个语法糖,在编译时会变成函数调用
@myDecorator2          ====>   myDecorator2(A) // 调用顺序4,最后调用类本身修饰器,从下往上依次调用
@myDecorator           ====>   myDecorator(A) // 调用顺序3,最后调用类本身修饰器,从下往上依次调用
class A {
  @myDecorator         ====>   myDecorator(A.prototype, "hello");   // 调用顺序1,先是非静态成员
  hello() {}
  
  @myDecorator         ====>   myDecorator(A, "staticHello"); // 调用顺序2,然后是静态成员
  static staticHello() {}
}

# 反射

// ********
// 什么是反射
// ********
// 程序在运行的时候直接获取或动态操作自身内容的一项技术
function MyDate() {
  // ....
}

// 旧写法
Object.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});

// 新写法
Reflect.defineProperty(MyDate, 'now', {
  Value: () => Date.now()
});


// **********
// Reflect Metadata
// ***********
// ES6暂不支持,元数据编程
class A {
  @Reflect.metadata("foo", "bar");
  hello(name: string): void {}
}

console.log(
  Reflect.getMetadata("design:paramtypes", new A(), "hello"), // 返回: [String]
  Reflect.getMetadata("foo", new A(), "hello"), // 返回: "bar"
);

# 强化练习

# 实现依赖注入

/** 文件存储服务 */
abstract class StorageService {
  abstract addFile(file: File);
}
class MysqlService extends StorageService {
  addFile(file: File) {
    console.log("[MysqlService] 通过MySQL添加文件:", file);
  }
}
class OssService extends StorageService {
  addFile(file: File) {
    console.log("[OssService] 通过OSS添加文件:", file);
  }
}


/** 图片裁剪上传库服务 依赖存储服务 */
function Injectable(target: any) {}

/** 图片处理服务 */
@Injectable
class ImageService {
  // 声明对 存储服务 的依赖
  constructor(private storage: StorageService) {}
  
  uploadImage(image) {
    console.log('[ImageService] 进行图片裁剪等');
    this.storage.addFile(image); // 上传图片
  }
}

/** 图片应用APP */
@Injectable
class App {
  // 声明对服务 图片服务 的依赖
  constructor(private imageService: ImageService) {}
  
  onUpload(image: File) {
    this.imageService.uploadImage(image);
  }
}

/** 通过IoC容器进行DI依赖注入 */
const app = container.inject(App);

app.onUpload("[FAKE IMAGE]");

/* console输出: */

// [ImageService] 进行图片裁剪等
// [MysqlService] 通过MySQL添加文件: [FAKE IMAGE]

/** IOC容器实现 */
const container = {
  // 服务提供者
  providers: {
    [ImageService]: ImageService,
    [StorageService]: MysqlService,
    // 后续可随时替换为OSS云存储
    // [StorageService]: OssService
  },
  // 注入器
  inject(cls: Function) {
    const tokenTypes = Reflect.getMetadata(
      "design:paramtypes", cls
    );
    // 递归进行 依赖注入
    const params = tokenTypes ? tokenTypes.map((tokenType) => this.inject(this.providers[tokenType])) : [];
    return Reflect.construct(cls, params);
  }
};

# 实现Url Query Split

// 通过TS类型系统 将query str -> query keyvalue
type QueryStr = 'a=1&b=b1&c=c1';
// =>
type QueryKeyValue = Split<QueryStr>; // { a: '1'; b: 'b1'; c: 'c1' }


// 步骤1:a=1&b=b1&c=c1 => ['a=1','b=b1','c=c1']
type Split<S> = S extends `${infer Left}&${infer Right}` ? [Left, ...Split<Right>] : [S]; // 递归,依次传入a=1&b=b1&c=c1、b=b1&c=c1、c=c1
type Step1 = Split<QueryStr>;

// 步骤2:['a=1','b=b1','c=c1'] => [{a:'1'}, {b:'b1'},{c:'c1'}]
type StrToKeyValue<S> = S extends `${infer Key}=${infer Value}` ? { [key in Key]: Value } : never;
  type KeyValueTuple<ST> = ST extends [infer Left, ...infer Right] ? [StrToKeyValue<Left>, ...(Right extends [] ? [] : KeyValueTuple<Right>)] : [StrToKeyValue<ST>]; // 递归,依次传入['a=1','b=b1','c=c1']、['b=b1','c=c1']、['c=c1']
type Step2 = KeyValueTuple<Step1>;

// 步骤3:[{a:'1'}, {b:'b1'},{c:'c1'}] => {a:'1',b:'b1',c:'c1'}
type TupleToIntersection<T> = T extends [infer Left, ...infer Right] ? Left & TupleToIntersection<Right> : unknown;
type Step3 = TupleToIntersection<Step2>;
最近更新时间: 2023/7/24 20:46:22