接着上次TypeScript进阶(一),这次把剩下的知识补充上。
TypeScript进阶(二)
类型推论
类型推论又叫类型推断。
之前对类型推论的基础做了笔记,这次写的是多类型联合和上下文类型。
多类型联合
多类型联合的推论是指根据等号右边推论出左边。
// 多类型联合
let arr7 = [1, 'abcd']; // 根据右边的[1, 'abcd'],推断出arr7是(string | number)[]类型
arr7 = ['a', 4, 'd']; // 这里再次赋值(元素是number或string)是可以的
// arr7 = [0, true]; // error
上下文类型
上下文类型的推论是指根据等号左边推论出右边。
举例。
// 上下文类型
window.onmousedown = (mouseEvent) => { // 根据window.onmousedown这个对象,这里推断出mouseEvent是MouseEvent类型
console.log(mouseEvent);
}
类型兼容性
深层次递归检测
// 类型兼容性,深层次递归检测
interface Geometry {
count: number,
info: {
background: string,
faceColor: string
}
}
let geom1: Geometry;
geom1 = {
count: 10,
info: { // 这里会往下检测info对象
background: 'black',
faceColor: 'lightblue'
}
}
函数兼容性
函数参数个数
赋给函数的参数个数要少于函数定义的参数个数。
举个例子。
forEach((item, index, arr) => {
// ...
});
// 这个函数定义了三个参数,但实际上,我们使用的时候,可以只传一个参数
forEach((item) => {
// ...
});
函数的参数类型
// 两个函数的参数类型相同
let xx = (a: string) => 0;
let yy = (a: string) => 0;
xx = yy; // 没问题
// 两个函数的参数类型不同
let xx = (a: string) => 0;
let yy = (a: number) => 0;
xx = yy; // 报错,因为参数类型不一样
函数的可选参数和剩余参数
const getSum = (arr: number[], callback: (...args: number[]) => number): number => {
// 把第一个参数数组传给回调函数
// 回调函数返回接收了第一个参数的回调函数
return callback(...arr);
}
const sum1 = getSum([1, 2], (...args: number[]): number => {
// 回调函数返回参数数组每个元素的累加和
return args.reduce((a, b) => {
return a + b;
}, 0);
});
const sum2 = getSum([1, 8, 0], (arg1: number, arg2: number, arg3: number): number => {
return arg1 + arg2 + arg3;
});
console.log(sum1); // 3
console.log(sum2); // 9
函数参数双向协变
let func1 = (n1: number | string): void => {}
let func2 = (n1: number): void => {}
// func1 = func2; // error
func2 = func1; // right
返回值类型
let x = (): string | number => 0
let y = (): string => 'a'
x = y // right
// y = x // error
函数重载
function merge(arg1: number, arg2: number): number
function merge(arg1: string, arg2: string): string
function merge(arg1: any, arg2: any) {
return arg1 + arg2;
}
console.log(merge('1', '2').length);
// 2
function sum(arg1: number, arg2: number): number
function sum(arg1: any, arg2: any): any {
return arg1 + arg2;
}
let func = merge; // 这里func的类型是一个对象接口,会有两个重载的函数
// func = sum; // error 如果再把sum赋值给func,因为sum少了一个重载函数,所以会报错
枚举兼容性
enum Status1 {
On,
Off
};
enum Status2 {
Dog,
Cat
};
let s1 = Status1.On;
s1 = 1; // right
s1 = 2; // right
// s1 = Status2.Dog; // error 不兼容
类的兼容性
类实例成员
class A1 {
static age: number;
constructor(public name: string) {}
}
class B {
static age: string;
constructor(public name: string) {}
}
class C {
constructor(public name: number) {}
}
let a11: A1;
let b: B;
let c: C;
b = new B('Jack');
a11 = b; // 这里没问题,类A1、B的实例都是传一个string类型参数,所以把b赋给a11实际上没问题
c = new C(10);
// a11 = c; // 这里有问题,类A1的实例传string类型参数,类C的实例传number类型参数,所以把c赋给a11有类型兼容问题
类的受保护成员
private
class A2 {
age: number;
constructor(age: number) {
this.age = age;
}
}
class B2 extends A2 {
constructor(age: number) {
super(age)
}
}
class C2 {
private age: number
constructor(age: number) {
this.age = age
}
}
const a22: A2 = new B2(10); // 子类可以赋值给父类类型的变量
// const a23: A2 = new C2(10); // 类C2含有私有属性,不能赋值
protected
class A3 {
protected age: number;
constructor(age: number) {
this.age = age;
}
}
class B3 extends A3 {
constructor(age: number) {
super(age)
}
}
class C3 {
protected age: number
constructor(age: number) {
this.age = age
}
}
const a3: A3 = new B3(10); // 子类可以赋值给父类类型的变量
// const a32: A3 = new C3(10); // C3类并不是A3类的子类,同时age属性受保护,这里有问题
泛型兼容性
interface Data<T> {}
let data1: Data<number>
let data2: Data<string>
data1 = {}
data2 = data1; // 这种情况是可以的
高级类型(一)
交叉类型
交叉类型是指多个类型的并集,使用&符号表示。
const mergeFunc = <T, U>(arg1: T, arg2: U): T & U => {
let res = {} as T & U;
res = Object.assign(arg1, arg2);
return res;
}
console.log(mergeFunc({ a: 1 }, { b: 'b' }));
// {a: 1, b: "b"}
联合类型
const getLengthFunc = (content: string | number): number => {
return typeof content === 'string' ? content.length : content.toString().length;
}
console.log(getLengthFunc('abcd'));
// 4
console.log(getLengthFunc(1111009));
// 7
类型保护
定义方法进行类型保护
function isString (value: string | number): value is string {
return typeof value === 'string';
}
const getLen = (arg: string | number): number => {
if(isString(arg)) {
return arg.length;
} else {
return arg.toString().length;
}
}
console.log(getLen('aaaa'));
// 4
console.log(getLen(123456789));
// 9
typeof类型保护
如果是简单的判断,可以直接使用typeof来做类型保护,而如果是比较复杂的判断,则使用定义函数的方式来进行类型保护。以上例子改写成使用typeof的类型保护方式如下。
const getLen = (arg: string | number): number => {
if (typeof arg === 'string') {
return arg.length;
} else {
return arg.toString().length;
}
}
console.log(getLen('aaaa'));
// 4
console.log(getLen(123456789));
// 9
typeof只对string、number、boolean、symbol这几种类型做保护。
typeof只做 === 或 !== 判断的类型保护,像includes这样的判断不能实现类型保护。
instanceof做类型保护
class cClass1 {
age = 18;
constructor() {}
}
class cClass2 {
name = 'Jack';
constructor() {}
}
function getRandomItem () {
return Math.random() > 0.5 ? new cClass1() : new cClass2();
}
const r1 = getRandomItem();
if (r1 instanceof cClass1) {
console.log('This is a instance of cClass1');
} else {
console.log('This is a instance of cClass2');
}
// 会根据随机数的大小,返回相应实例的构造函数log
null/undefined
null和undefined是任何类型的子类型。
区别对待null和undefined
// number | undefined 、 number | null 、 number | null | undefined 是不一样的
const getSum3 = (a: number, b?: number) => {
return a + (b || 0);
}
console.log(getSum3(1));
类型断言
// 类型断言,使用"!"手动指明类型不是null或undefined
function getSplitStr (arg: number | null | undefined): string {
arg = arg || 0.1;
function getRes(prefix: string) {
return `${ prefix }_${ arg!.toFixed().toString() }`
}
return getRes('Y');
}
console.log(getSplitStr(7.3));
// Y_7
console.log(getSplitStr(null));
// Y_0
console.log(getSplitStr(undefined));
// Y_0
类型别名
定义树状结构类型
type Childs<T> = {
current: T,
child?: Childs<T>
}
const c9: Childs<object> = {
current: {},
child: {
current: {}
}
}
类型别名与接口兼容
// 类型别名与接口兼容
type Alias = {
num: number
}
interface Inter {
num: number
}
let alias: Alias = {
num: 123
}
let inter: Inter = {
num: 1
}
alias = inter; // 没问题
字面量类型
字符串字面量类型
// 字符串字面量类型
type StrType = 'str_type';
// let str: StrType = 's'; // error
let str: StrType = 'str_type' // right
// 字符串字面量的联合类型
type Direction = 'North' | 'East' | 'West' | 'South';
function getDirectionFirstLetter(direction: Direction) {
return direction.substr(0, 1);
}
console.log(getDirectionFirstLetter('East'));
// E
数字字面量类型
// 数字字面量类型
type Age = 18;
interface Info {
name: string,
age: Age
}
let p1: Info = {
name: 'Tom',
age: 18 //这个值必须跟Age类型别名的值一致
}
// let p1: Info = {
// name: 'Tom',
// age: 19 // error
}
可辨识联合
可辨识联合的两要素:1、具有普通的单例类型属性;2、一个类型别名包含了多个类型的联合。
interface Square {
kind: 'square',
size: number
}
interface Rectangle {
kind: 'rectangle',
height: number,
width: number
}
interface Circle {
kind: 'circle',
radius: number
}
type Shape = Square | Rectangle | Circle;
function getArea (s: Shape) {
switch (s.kind) {
case 'square': return s.size * s.size; break;
case 'rectangle': return s.width * s.width; break;
case 'circle': return Math.floor(Math.PI) * s.radius ** 2; break;
}
}
console.log(getArea({
kind: 'rectangle',
width: 100,
height: 100
}));
// 10000
console.log(getArea({
kind: 'square',
size: 4
}));
// 16
console.log(getArea({
kind: 'circle',
radius: 3
}));
// 27
完整性检查
function assetNever (value: never): never {
throw new Error(`Unexpected object ${ value }`);
}
function getArea (s: Shape): number {
switch (s.kind) {
case 'square': return s.size * s.size; break;
case 'rectangle': return s.width * s.width; break;
// case 'circle': return Math.floor(Math.PI) s.radius ** 2; break;
default: return assetNever(s)
}
}
这里写一个函数assetNever()用来检查switch代码的完整性,如果漏写了case,default中会检测到代码的问题。
高级类型(二)
this类型
使用this返回实例,实现链式调用。
class Counter {
constructor(public count: number) {}
public add (v: number) {
this.count += v;
return this;
}
public substract (v: number) {
this.count -= v;
return this;
}
}
var count1 = new Counter(3);
console.log(count1.add(10));
// Counter {count: 13}
console.log(count1.add(10).substract(1));
// Counter {count: 22}
console.log(count1.add(9).substract(9).add(1));
// Counter {count: 23}
class powCounter extends Counter {
constructor(public count: number = 0) {
super(count);
}
pow (v: number) {
this.count = this.count ** v;
return this;
}
}
var p2 = new powCounter(2);
console.log(p2.pow(3));
// powCounter {count: 8}
console.log(p2.substract(2));
// powCounter {count: 6}
索引类型
索引类型查询操作符
// keyof
interface InfoInterfaceAdvanced {
name: string,
age: number
}
let infoProp: keyof InfoInterfaceAdvanced
infoProp = 'name'
infoProp = 'age'
// 泛型使用索引类型
function getValue<T, K extends keyof T> (obj: T, names: K[]): T[K][] {
return names.map((n) => obj[n]);
}
console.log(getValue({ width: 100, id: 'A001' }, ['width', 'id']));
// [100, "A001"]
function getProperty<O, K extends keyof O>(o: O, key: K): O[K] {
return o[key];
}
interface Objs<T> {
[key: number]: T
}
let key1: keyof Objs<number>
interface Type {
a: never;
b: never;
c: string;
d: number;
e: boolean;
f: null;
g: undefined;
h: object
}
type Test = Type[keyof Type]
keyof不会查询出never类型的属性。
索引访问操作符[]
interface InfoInterfaceAdvanced {
name: string,
age: number
}
type NameTypes = InfoInterfaceAdvanced['name']; // type NameTypes = string
interface Objs22<T> {
[key: string]: T
}
let key22: Objs22<number>['age']
映射类型
基础
只读属性
interface InterInfo {
width: number,
color: string,
height: number
}
// 映射
type ReadonlyInfoMap<T> = {
readonly [P in keyof T]: T[P]
}
type ReadonlyInfo = ReadonlyInfoMap<InterInfo>;
这样,类型别名ReadonlyInfo就是由InterInfo映射成的只读属性类型列表。
可选属性
如果属性是可选,则
type ReadonlyInfoMap<T> = {
readonly [P in keyof T]?: T[P]
}
部分属性
type Pick1<T, K extends keyof T> = {
[P in K]: T[P]
}
type keyList = 'width' | 'color';
type ReadonlyInfo4 = Pick1<InterInfo, keyList>;
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick1<T, K> {
const res: any = {}
keys.map((key) => res[key] = obj[key]);
return res;
}
console.log(pick({ width: 100, radius: '50%' }, ['radius']));
// {radius: "50%"}
内置映射类型
也可以使用内置类型,Readonly、Partial、Pick、Record。
// Readonly
type ReadonlyInfo2 = Readonly<InterInfo>;
// Partial
type ReadonlyInfo3 = Partial<InterInfo>;
// Pick
type ReadonlyInfo4 = Pick<InterInfo, keyList>;
// Record
// 把对象属性值映射成其他属性值
function mapObject<K extends string | number, V, U> (obj: Record<K, V>, f: (x: V) => U): Record<K, U> {
let res: any = {}
for(const key in obj) {
res[key] = f(obj[key]);
}
return res;
}
const names = { a: 'aa', b: 'bbb', c: 'cccc' };
const len = mapObject(names, (s) => s.length );
console.log(len);
// {a: 2, b: 3, c: 4}
由映射类型进行推断
举例。
type Proxy<V> = {
get(): V
set(newval: V): void
}
type Proxify<O> = {
/**
* 把属性值传给Proxy作为Proxy的泛型
* 每个属性都带有get、set
*/
[K in keyof O]: Proxy<O[K]>
}
function proxify<O>(obj: O): Proxify<O> {
const result = {} as Proxify<O>;
for (const key in obj) {
// 每个属性都带有set、get
result[key] = {
get: () => obj[key],
set: (newval) => obj[key] = newval
}
}
return result;
}
let modal = {
name: 'cancel',
width: 500,
msg: 'success',
radius: '5%'
}
let proxyProps = proxify(modal);
console.log(proxyProps);
// {name: {…}, width: {…}, msg: {…}, radius: {…}}
console.log(proxyProps.name.get());
// cancel
// 拆包
function unproxify<O> (obj: Proxify<O>): O{
const result = {} as O;
for (const key in obj) {
// 每个属性还原回原来的属性值
result[key] = obj[key].get()
}
return result;
}
console.log(unproxify(proxyProps));
// {name: "cancel", width: 500, msg: "success", radius: "5%"}
增加和移除修饰符
添加修饰符使用+,移除修饰符使用-。
// 移除修饰符 -
type RemoveReadonlyInfo<T> = {
-readonly [P in keyof T]-?: T[P]
}
type NotReadonly = RemoveReadonlyInfo<InterInfo>
元组和数组上的映射类型
type MapToPromise<T> = {
[K in keyof T]: Promise<T[K]>
}
type Tuple = [number, string, boolean];
type promiseTuple = MapToPromise<Tuple>;
let t1: promiseTuple = [
new Promise((resolve, reject) => {resolve(1)}), // 1 对应T[K]
new Promise((resolve, reject) => {resolve('1')}), // '1' 对应T[K]
new Promise((resolve, reject) => {resolve(true)}) // true 对应T[K]
];
console.log(t1);
unknown类型
1、任何类型都可以赋值给unknown类型
// 1、任何类型都可以赋值给unknown类型
let value2: unknown;
value2 = 123;
value2 = 'abcd';
2、如果没有类型断言或基于控制流的类型细化时,unknown不可以赋值给其他类型,此时它只能赋值给unknown和any类型
let value11: unknown;
let value2: unknown;
value11 = value2;
3、如果没有类型断言或基于控制流的类型细化时,不能进行任何操作
let value4: unknown;
// value4 += 1; // error
4、unknown与任何其他类型组成的交叉类型,最后都等于其他类型
type type1 = string & unknown; // string
type type2 = unknown & unknown; // unknown
5、unknown与任何其他类型(除了any)组成的联合类型,最后都等于unknown类型
type type5 = unknown | string; // unknown
type type6 = any | unknown; // any
type type7 = unknown | number[] // unknown
6、never类型是unknown类型的子类型
type type8 = never extends unknown ? true : false; // true
7、keyof unknown 等于类型never
type type9 = keyof unknown; // never
8、只能对unknown做等或不等的操作,不能进行运算等其他操作
let value11: unknown;
let value2: unknown;
value11 !== value2
value11 === value2
// value11 += value2; // error
9、unknown类型的值不能访问它的属性、作为函数调用和作为类创建实例
let value: unknown;
// value.aa // error
// value() // error
// new value() // error
10、使用映射类型时,如果遍历的是unknown类型,则不会映射任何属性
type Type11<T> = {
[P in keyof T]: number
}
type type11 = Type11<any>; // { [x: string]: number }
type type12 = Type11<unknown>; // {}
条件类型
基础
形如:
T extends U ? x : y
条件类型举例。
type Type2<T> = T extends string ? string : number;
let index: Type2<'a'> // let index: string
let index2: Type2<1> // let index2: number
let index3: Type2<false> // let index3: number
type TypeName<T> = T extends any ? T : never;
type type13 = TypeName<string | number>; // string | number
分布式条件类型
type TypeList<T> =
T extends string ? string :
T extends number ? number :
T extends boolean ? boolean :
T extends undefined ? undefined :
T extends () => void ? () => void :
object
type Type14 = TypeList<number[]> // object
type Type15 = TypeList<() => void> // () => void
type Type16 = TypeList<string> // string
type Diff<T, U> = T extends U ? never : T;
type Type17 = Diff<string | number | boolean, undefined | number>; // string | boolean
type Type18<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T] // keyof不返回never类型的属性名,除了函数,其他都是never
interface Part {
id: number,
name: string,
subparts: Part[],
updatePart(name: string): void
}
type Type19 = Type18<Part> // updatePart
条件类型的类型推断-infer
type Type20<T> = T extends any[] ? T[number] : T; // T[number]可以看作是返回每个元素对应的类型
type Type21 = Type20<string[]> // string
type Type21 = Type20<(string | number)[]> // string | number
type Type22 = Type20<number> // number
type Type20<T> = T extends any[] ? T[number] : T;
// 等价于使用infer进行的推断
type Type23<T> = T extends Array<infer U> ? U : T;
ts预定义内置条件类型
Exclude<T, U>
type Etype1 = Exclude<'a' | 'b' | 'c', 'c'>
Extract<T, U>
type Etype2 = Extract<'a' | 'b' | 'c', 'c'>
NonNullable<T>
type Ntype1 = NonNullable<string | number | boolean | null | undefined>;
ReturnType<T>
type Rtype1 = ReturnType<() => string> // string
type Rtype1 = ReturnType<() => void> // void
InstanceType<T>
T需要是一个构造函数或者是never、any类型。
type Itype1 = InstanceType<typeof Iclass> // 需要使用typeof标识这个Iclass是个类而不是一个值
type Itype2 = InstanceType<any>
type Itype3 = InstanceType<never>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。