名词解释
- Ambient simply means “without implementation”.
高级类型
type guards(类型守护) 和区分类型
-
as 关键字: pet as Fish
-
(T: any) => T is string: 用户自定义的断言方法
-
in 关键字: “swim” in pet
-
typeof 关键字: typeof x === “number”
-
instanceof 关键字: padder instanceof SpaceRepeatingPadder
type 和 interface
-
type 可以重命名 primitives(原始类型), unions(联合类型), tuples(类型数组)
-
type 并不创建新类型, 只是给类型取了个新名字
-
interface 可以拓展并合并同名类型, type 则会报错
Enum
-
数字枚举在不指定初始化值时默认从 0 取值
-
如果上一个 key 的值为
number, 则下一个 key 的默认值为number + 1 -
如果上一个 key 的值不为
number, 则下一个 key 一定要有initializer -
枚举成员的值除了是常量,还可以是计算出来的结果
-
只有数字枚举类型可以用计算值
-
数字枚举值可以反向映射,字符串枚举值不行
-
枚举 key 不能是计算属性
-
枚举前面加个 const 可以减少开销
-
外部枚举 declare
多态的 this 类型
在类中可以使用 this 多态类型, 能很容易的表现连贯接口间的继承, 例如:
class ScientificCalculator extends BasicCalculator {
public constructor(value = 0) {
super(value);
}
public sin() {
this.value = Math.sin(this.value);
return this;
}
// ... other operations go here ...
}
let v = new ScientificCalculator(2)
.multiply(5)
.sin()
// 这里 this 类型继承了 add()
.add(1)
.currentValue();
索引类型(Index types)和 index signatures(字符串索引签名)
其中 key 的类型可以为:
-
number -
string -
symbol
可以使用 keyof 和 T[K] 来操作 index signature, 又因为 string 类型的 key 有可能被 number 类型的 key 所引用到, 所以:
interface A {
[key: string]: any;
}
type B = keyof A;
// type B = string | number
Mapped types 映射类型
由一个类型生成新类型的过程叫映射类型, 常用的关键字为:
-
keyof: 获取类型的key可选类型 -
in: 和key一起使用, 代表属于某联合类型 -
extends: 这里的继承类似于断言,A extends B即A属于B -
never: 可以进行类型判断 -
infer: 类型判断占位类型 -
&: 可能与索引类型共用来完成通用+指定类型配置 -
?: 移除参数的可选配置 -
new: new (…args: any) => any 代表构造函数
// 常用的映射类型如下
type Readonly2<T> = {
readonly [P in keyof T]: T[P];
};
type Partial2<T> = {
[P in keyof T]?: T[P];
};
type Nullable2<T> = {
[P in keyof T]: T[P] | null;
};
type Pick2<T, P extends keyof T> = {
[key in P]: T[P];
};
type Record2<T extends string | number | symbol, V> = {
[key in T]: V;
};
type Required2<T> = {
[P in keyof T]-?: T[P];
};
type TypeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object';
type Diff<T, U> = T extends U ? never : T;
type Filter<T, U> = T extends U ? T : never;
type Exclude2<T, K> = T extends K ? never : K;
type Omit2<T, P> = Pick<T, Exclude<keyof T, P>>;
type InstanceType2<T extends new (...args: any) => any> = T extends new (
...args: any
) => infer R
? R
: any;
type ConstructorParameters2<
T extends new (...args: any) => any
> = T extends new (...args: infer R) => any ? R : never;
// https://github.com/type-challenges/type-challenges/blob/master/questions/296-medium-permutation/README.md
type Permutation<T, K = T> = [T] extends [never]
? []
: K extends any
? [K, ...Permutation<Exclude<T, K>>]
: never;
Conditional Types(有条件类型)
T extends U ? X : Y 为有条件的类型, 如果没有充足的信息的话, 类型可能为 X | Y
Distribute conditional types(分布有条件类型)
在映射条件类型时, 会分别映射每个条件的类型,例如:
T extends U ? X : Y 在 T = A | B | C 时的结果为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
type T6 = TypeName<string | string[] | undefined>;
// ^ = type T6 = "string" | "undefined" | "object"
Type inference in conditional type(有条件类型的类型推断)
可以有多重类型判断
type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T;
type T0 = Unpacked<string>;
// ^ = type T0 = string
type T1 = Unpacked<string[]>;
// ^ = type T1 = string
type T2 = Unpacked<() => string>;
// ^ = type T2 = string
type T3 = Unpacked<Promise<string>>;
// ^ = type T3 = string
type T4 = Unpacked<Promise<string>[]>;
// ^ = type T4 = Promise
type T5 = Unpacked<Unpacked<Promise<string>[]>>;
// ^ = type T5 = string
多重类型判断有可能会合并类型
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
type T1 = Foo<{ a: string; b: string }>;
// ^ = type T1 = string
type T2 = Foo<{ a: string; b: number }>;
// ^ = type T2 = string | number
特殊的类型
ThisType
ThisType 一般会和 & 共用, ThisType 只会指定 this 的类型
// 没有ThisType情况下
const foo = {
bar() {
console.log(this.a); // error,在foo中只有bar一个函数,不存在a
},
};
// 使用ThisType
const foo: { bar: any } & ThisType<{ a: number }> = {
bar() {
console.log(this.bar); // error,因为没有在ThisType中定义
console.log(this.a); // ok
},
};
foo.bar; // ok
foo.a; // error,在外面的话,就跟ThisType没有关系了
Declaration Merging(声明合并)
interface 声明合并
-
非方法的属性合并时必须不重复或有相同的类型
-
方法属性在合并的时候, 后加载的会有更高的优先级, 但是 single string literal type (e.g. not a union of string literals) 会被冒泡到顶部, 优先级更高
namespace
-
全局作用域下, 不可以定义值属性, 而且所有的类型属性都会被暴露, 不需要显示写 export
-
模块作用域下, 可以定义值和类型, 只有 export 的属性才可以被外部访问到; 可以合并同名的 namespace 以及里面的 interface 等, 但是不能共享不同 namespace 下的值
namespace 可以和 class, function 和 enum 合并:
// namespace 和 class 合并
class Logger {
public logLevel = Logger.LogLevel.Info;
}
namespace Logger {
export enum LogLevel {
Verbose,
Info,
}
}
// namespace 和 function 合并
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel {
export let suffix = '';
export let prefix = 'Hello, ';
}
// namespace 和 enum 合并
enum Color {
red = 1,
green = 2,
blue = 4,
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == 'yellow') {
return Color.red + Color.green;
} else if (colorName == 'white') {
return Color.red + Color.green + Color.blue;
} else if (colorName == 'magenta') {
return Color.red + Color.blue;
} else if (colorName == 'cyan') {
return Color.green + Color.blue;
}
}
}
.d.ts 的加载规则
-
和 nodejs 模块加载类似
-
除了加载同级同名的 *.d.ts 文件, 还会依次加载当前文件夹的所有的 纯 *.d.ts 文件
-
如果在 *.d.ts 中使用了 export, 则和 *.ts 加载逻辑类似
全局作用域和模块作用域
类型区分
-
类型全局作用域: *.d.ts 如果没有 import/export, 并且没有同名的 .ts/.js 文件
-
类型的类似 ts 模块作用域: *.d.ts 文件包括 import/export, 不能使用 /// <reference 引入
-
类型的模块作用域: *.d.ts 如果有同名的 .ts/.js 文件, 可以使用 /// <reference 引入, 但是拿不到全局类型
-
ts 的全局作用域: *.ts 文件不包括 import/export
-
ts 的模块作用域: *.ts 文件包括 import/export, 本地类型可以覆盖全局类型, 并不会与全局类型冲突
特点区分
-
是否可以被 import 或 export
-
是否可以使用全局类型
-
类型是否为全局类型
-
本地类型是否可以覆盖全局类型
-
是否可以被 /// <reference 引入
Reference
https://github.com/type-challenges/type-challenges/blob/master/README.zh-CN.md
Discussion
留言与讨论
想法、补充和不同意见都欢迎。