# 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>;
← 介绍 TypeScript类型系统实战课 →