导读
全面体系的进行TS认识,要想学好 TypeScript 必备的基础知识
正文
什么是类型
一种约束
ES类型系统
ES七种基本数据类型: [散乱的类型,不成体系]
- Boolean
- String
- Number
- undefined
- null
- Symbol
- BigInt
- Object [类,数组,函数]
TS类型特性: [成体系的,自顶向下划分的]
- 子类型可以赋值给父类型, 父类型不能赋值给子类型, 相同类型可以赋值
- 详细说明如下文
TS类型系统
TS基础(非复合)类型介绍
体系划分 [自顶向下]
顶端类型
any
-
any
代表的是任意类型 - 使用场景:为了跳过所有编译类型检查
-
const a: any = 0; a.length;
运行error,编译的时候不会报错 -
any
是所有类型的父类型, 所有类型的变量都可以赋值给any
类型的变量 - 但特殊的是
any
也可以赋值给所有类型
unknown
- 代表未知类型, 是一种更安全的顶端类型
- 所有类型都可以赋值给
unknown
类型 -
unknown
只能赋值给unknown
和any
, 不能给其他类型赋值 - 使用场景:代表一种未知类型
function f(m: unknown) {
if (typeof m === 'string') return m.length; //如果使用就必须细化
}
原始类型
boolean
const yes: boolean = true;
string
const s: string = '';
number
const n: number = 1;
undefined
const f: undefined = undefined;
null
const n: null = null;
bigint
const int: bigint = 10n;
symbol
- 只有字面量类型(不可变的)
- 在ES里,
Symbol()
代表一个唯一值, 用作对象属性 - TS里
symbol
是Symbol()
实例化后的类型
let xx = Symbol('name'); //xx可变,因为只是个表达式
let say = Symbol('say');
// ES里这样写没问题
let obj = {
[xx]: 'lnj',
[say]: function() {
console.log('say')
}
}
// 加上类型描述后,obj的类型无法描述,因为xx是表达式
let obj: {
[xx]: string, // error
[say]: Function
} = {
[xx]: 'lnj',
[say]: function() {
console.log('say')
}
}
- unique symbol的出现,用作接口,类的可计算属性名
- 为了增加symbol类型下的字面量值 定义了一个不可改变的字面量类型 unique symbol
const a: unique symbol = Symbol() // 必须用const申明,代表不可改变
const a = Symbol() // a 也是unique symbol类型
// unique symbol 为 symbol的子类,代表不可变的字面量
const b: unique symbol = Symbol();
interface xx {
[b]: string
}
void [无类型]
- 表示函数没有返回值
function a(): void {}
function a(): void { return undefined; }
enum [枚举类型]
- 多个成员, 每个成员是个常量
- 使用场景:不依赖枚举成员值, 降低代码耦合度
enum D {
left, //0
right //1
}
function a(d: D): void {
switch (d) {
case D.left:
break;
case D.right:
break;
}
}
- 异构枚举 [不推荐]
enum D {
up, // 0
done, // 1
left = "left",
right = 4,
iner // 5
ccc = Math.pow(2, 3) // 计算
}
- 枚举编译后的正反映射
enum D {
left,
right
}
const d: D = D.left;
console.log('d>>>', d); //0
console.log('D.left>>>', D.left); // 0
console.log('D[D.left]>>>', D[D.left]); // left
console.log('D[0]>>>', D[0]); // left
//编译后
(function(D) {
D[D["left"] = 0] = "left";
D[D["right"] = 1] = "right";
})(D || (D = {}));
- const枚举
// JS里面没有枚举类型,枚举类型为常量时,编译后完全删除
const enum Dd {
left,
right
}
const dd = [ // Array<Dd>
Dd.left,
Dd.right
]
//编译后,只是添加了注释
const dd = [
0 /* Dd.left */ ,
1 /* Dd.right */
];
- 联合枚举类型 [枚举成员为常量]
enum D {
left,
right
}
// <=> 等价于ß
type Dd = D.left | D.right;
非原始类型
数组类型
// 定义方式
const a: number[] = [1, 2]; //简便
const b: Array < string > = ['c', 'd']; //泛型
// 只读数组定义方式
const a: readonly number[] = [1, 2];
const b: ReadonlyArray < string > = ['c', 'd'];
const c: Readonly < string[] > = ['c', 'd']; // Readonly<T>
// 不能将只读数组赋值给常规数组 ,不能通过赋值放宽约束
const a: readonly number[] = [1, 2];
const b: Array < number > = a; // error
元祖类型 [数组是元组的父类]
// 对数组类型的进一步限定, 对数组长度的限定, 对数组元素类型的限定
// 使用方法
const a: [number, string] = [1, ''];
const b: [number, string ? ] = [1]; //可选
const c: [number, ...boolean[]] = [3, true]; //剩余
// 因为长度有限定,所以获取不存在报错
const a: [number, string] = [1, ''];
a[3] //error
Object [首字母大写]
- 定义
- Object 类型是特殊对象 Object.prototype 的类型, 描述JavaScript中所有对象都共享( 原型继承) 的属性和方法
- 兼容性
- 是一个顶层的对象类型, 除了 undefined 和 null 都可以赋值给 Object 类型( 因为原始类型, 原始值有‘ 自动封箱操作’)
- 因为Object本身就是顶层类型了, 只能赋值给 any / unknown / Object / object / {}
- 自动封箱
- 在原始值上调用某个方法, 会将其转化成对象类型再调用 比如:
3..toString() //两个点
- JavaScript 中一切皆为对象, 为每个原始值提供了构造函数创建能够表示原始值的对象 比如: String() ,Number()
let b: Object = '1'; // new String('1');
- 应用场景
- 编译后可以运行, 但这是一个使用错误, 不应该使用Object描述自定义对象, 它描述最顶层的对象类型, 只有公共属性
let b: Object = { a: 111 };
console.log(b.a); //error Object 上 不存在 a
- Object 只描述 Object.prototype 的类型
object [首字母小写]
- 意义
- 对于原始值类型 boolean number string undefined null..都有类型描述
- Object 也无法描述非原始值类型, 因为原始值可以给 Object 类赋值, 所以需要一种类型描述非原始值类型的顶层
- 定义
- 表示除原始值意外的非原始值类型
- 比如:对象类型
- object 与 Object的对比
- 不能理解为 Object 是 object 的父类, 它们只是描述的范围不同
let b: Object = '1';
let d: object = '1'; // error
let c: object = new String('1'); //只是不能对原始值自动封箱
- 兼容性
- 是一个顶层的对象类型, 除了原始类型外的对象类型都可以赋值给 object 类型,比如
let c: object = new Date();
let d: object = {}
- object 类型可以赋值给 any / unknown / Object / object / {}
- 场景
- 与 Object 类似, object 描述 对象类型的顶层, 也只会有公共属性
let b: object = { a: 111 };
console.log(b.a); //error object 上不存在 a
- 能够跟精确的描述 Object.create() 方法签名的类型 ObjectConstructor
interface ObjectConstructor {
create(o: any..) // 没有 object之前只能用any
create(o: object | null..) // 更好描述
}
Object.create(1) // error
Object.create 的参数
function f(o: object | null): object {
return Object.create(o);
}
对象字面量类型
- 定义
- 将一个对象字面量值当作类型
- 对象类型字面量的类型成员
const b = "B";
const c: unique symbol = Symbol();
let o: {
// 属性签名,必须是 string,number,unique symbol的字面量类型
a: number, // Type 可以为any类型
[b]: string,
[c]: boolean,
d ? : bigint, // 可选属性
readonly e: null // 只读属性
// 索引签名,n表示一个占位变量
[n: number]: undefined
// 调用签名
(): void // o() 对象字面量类型可以表示函数类型
// 构造签名
new(): object // new o() 对象字面量类型可以表示类类型
// 方法签名
sub(x: number): void // 函数类型的使用成员
};
// 调用签名用法
let add: {(): void } = function() {
console.log(1);
}
// 构造签名用法
let Abb: { new (name: string): object } = class {
private name: string;
constructor(name: string) {
this.name = name;
}
}
- Object ,{},object 区别,
- {}和Object可以互换
let o1: Object = 1;
let o2: {} = 1;
o1 = o2;
o2 = o1;
- Object: 顶级对象类型, 强调对象公共属性和方法,一般不会用來直接申明
- {}: 强调不包含属性的对象类型, 相当于Object,可以作为Object的代理对象 < T > 如泛型参数如果, 没有约束条件, 默认的约束就是 {}
- object 强调非原始类型的公共属性和方法
- 忽略多余属性检查的5种方式
const p0: { x: number } = { x: 1, y: '' } as { x: number } // 1 使用 as 强制转换
"suppressExcessPropertyErrors": true // 2 配置tsconfig
// @ts-ignore
const p1: { x: number } = { x: 1, y: '' } // 3 使用注释指令
const p2: { x: number, [p: string]: number } = { x: 2, y: 1 } // 4 目标对象添加索引名
const temp = { x: 0, y: 1 } // 5 使用一个临时变量,另源对象不再是全新的字面量类型,编译器不再执行多余的属性检查
const p3: { x: number } = temp;
函数类型
- TS函数是如何定义的 [包括 参数,返回值,重载]
// [重载签名]
function f(m: number, n ? : string): void; //常规参数 可选参数 f(1,'') || f(1)
function f(...args: [number, string ? ]): number; //元组展开剩余参数,可选 f(1) || f(1,'')
function f(b: boolean, ...args: number[]): string; //数组展开剩余参数 f(true) || f(true,1,2,3,...)
function f({ x, y }: { x: number, y: number }): boolean; //解构参数 f({true,2})
function f(...args: any[]): never { //这行及以下为函数 [实现签名]
/**
* 重载签名必须与实现签名相符
* 参数类型:兼容重载类型(并集)
* 返回值类型:可以赋值给任意重载签名得返回值(交集)
*/
throw new Error();
}
- 函数的类型字面量定义
// 普通函数
let d: () => void = function(): void {};
// 构造函数
let g: new() => void = class { constructor() {} };
// 无法描述重载函数类型
- 函数类型的对象字面量定义 [函数也是对象]
// 普通函数
let dd: {
(): void } = function(): void {};
// 构造函数
let gg: new() => void = class { constructor() {} };
// 重载函数
let ff: {
(n: number): string,
(n: string): number
} = function(p: string | number): number & string {
throw new Error();
}
- 函数类型字面量 与 对象类型字面量 定义函数类型对比
// 函数定义
function fn(x: number): void {};
fn.version = 1;
// 函数字面量类型描述
let foo: (x: number) => void = f; // 函数字面量类型更加简洁
foo.version // error
// 对象字面量描述函数对象
let foo2: { // 对象字面量表达能力更强
(x: number): void,
version: number
} = fn;
foo2.version // 1
- 包装函数的定义方式 // 既可以作为普通函数,又可以作为构造函数
/**
* 顶级声明必须以declare开头
* declare声明的变量和模块后,其他地方不需要引入
*/
declare const F: {
(n: number): Number,
new(n: number): number
}
const a: Number = F(1);
const b: number = new F(1);
// 比如TS内部实现的 declare var Number: NumberConstructor;
// NumberConstructor描述一个包装函数 Number 的类型, 但Number具体的实现不是TS定义的, 是JS本身固有的
// 所以不必在意 Number 的具体实现方式
- 重载函数的特性
// 重载函数的执行顺序是从上往下,要保证跟精确的调用在靠前位置
function f(x: any): number;
function f(x: string): 0 | 1;
function f(x: any): any {};
const a: 0 | 1 = f(''); // error 不能将类型“number”分配给“0|1”
// 接口及对象字面量可以定义重载函数, 函数类型字面量无法定义重载函数
// 在返回值不变的情况下尽量少用重载函数, 用灵活的参数设置代替会更加简洁
// 函数实现的函数签名不是重载函数的调用签名
- 函数中的this类型
function f() {
this.a = 1; // error "this" 隐式具有类型 any
}
function f1(this: { a: number }) { // 作为可选参数,放在参数类型的第一个规定位置
this.a = 1;
}
function f2(this: void) { // 定义纯函数的话可将this设置为void
this.a = 1; // 类型“void” 上不存在属性“a”
}
接口
- 特点
- 相当于一种有名称的对象字面量类型, 并且可以定义类型参数 < T >
- 同名接口申明具有可合并的特点, 申明只存在编译阶段
- 接口是对对象字面量描述的另一层扩展
- js里大多数可以用对象字面量描述,一切皆为对象,比如一个类的实例或者一个函数的返回值
- 定义方法
interface AA {
name: string, // 属性签名
x ? : sting, // 可选属性
readonly y: string, // 只读属性
(message ? : string): Error, // 调用签名
new(message ? : string): Error, // 构造签名
// 三种可互换
getId(id: string): string | null, //方法签名
getId: (id: string) => string | null, //属性签名+函数字面量
getId: {(id: string): string | null }, //属性签名+对象字面量
// 重载方法
f(): number,
f(x: boolean): string,
f(x: string, y: string): string,
['f'](): string, //计算属性签名
[prop: number]: number, //索引签名。如果同时存在字符串索引和数值索引,数值索引类型必须能赋值给字符串索引类型
}
- 接口继承
// 子类型继承父类同名属性的成员类型需要兼容->子类型能够赋值给父类
interface F() {
f(): { a: string }
}
interface G() {
f(): { b: number }
}
interface S extends F, G {
f(): { a: string, b: 1 } // 做兼容,不然报错
}
类类型[类的实例类型] [描述的是类生成的实例的类型]
- 详见 定义类的扩充 - TS的类
类构造函数类型[描述的是类生成的本质上是一个构造函数类型, 可以用一个有构造签名的对象字面量表示实例的类型]
- 详见 定义类的扩充 - TS的类
尾端类型
never [代表一种不可能的类型]
- 是最末端的类型, 是所有类型的子类型
- 能赋值给所有类型, 只有never类型能赋值给never类型, any也不可以
let x: never;
let y: boolean = x; //严格模式编译报错 不允许x在申明之前使用,因为没有值是never
const a: number = '1'
as never;
const b: object = '2'
as never;
const c: never = '2'
as never;
console.log(a); // 1
console.log(b); // 2
- 使用场景
// 程序异常
function f(): never {
while (true) {
// statement
}
}
function f(): never {
throw new Error()
}
// 类型推断:TS有静态类型检查,类型推断并不是推断出来某一个具体的值,而是推断出一个变量的类型
// 1
function f(p: string) {
if (typeof p === 'string') {
p // string
} else {
p // never
}
}
// 2
enum D {
left,
right
}
function fn(d: D) {
switch (d) {
case D.left:
d // D.left
break;
case D.right:
d // D.right
break;
default:
d // never
break;
}
}
定义类的扩充
类型别名 [可指代任意类]
- 类别名可以指代任意类型, 包括原始类型, 对象类型, 接口, 联合类型, 交叉类型
interface C { d: number };
type t0 = C;
type T = number | string;
type T0 = { a: number, b: number } & { c: number, b: string } // 既是又是
- 递归的类型别名。 定义一些常用的类
/**
* 递归类型别名
* */
// 对象字面量
type T0 = null | {
right: T0,
left: T0,
value: number
}
let tree: T0 = {
right: {
right: null,
left: null,
value: 0
},
left: null,
value: 1
}
// 函数字面量,构造函数类型
type T1 = () => T0;
type T2 = new() => T1;
// 接口类型
interface A < T > {
[p: string]]: T
}
type T3 = A < T3 > | undefined | 'Bob'
let a: T3 = {
job: undefined,
name: {
fristName: 'Bob',
lastNAme: undefined
}
};
// 数组/元组类型
type T4 = null | [number, T4];
let c: T4 = [1, [2, null]]
// 泛型类
class B < T > {
name: T | undefined
}
type T5 = { fristName: string } | B < T5 > ;
let x: T5 = new B < { fristName: 'Jone' } > ();
- 类型别名与接口对比
- 指代范围不同: 类别名可以指代任何类型, 接口只能表示对象类型
- 继承实现不同: 接口有继承功能, 类别名的继承功能只能通过交叉类型来实现类似的效果
type S = { a: string };
type O = S & { r: number };
let a: O = { a: '2', r: 123 };
- 接口有申明合并的特性, 而类型别名不能重复申明
interface A { x: number }
interface A { y: number }
// <=>
interface A { x: number, y: number }
类 [TS对类进行了扩展]
- TS中的类成员是如何定义的
class A {
name: string = 'A';
} // 类的申明
let B = class C {
name: string = 'C';
} // 类的表达式
// [成员变量/函数/存取器/索引成员]
class C {
name: string = 'C'; // 严格模式必须初始化
age: number; // 或在构造函数中初始化
occupation ? : stirng; // 间接初始化要加?非空断言
/**
* 只读属性不能简介初始化,只能在申明初始化或者构造函数中初始化
* 如果不确定是否只读,可以添加readonly修饰符,需要修改再去掉
*/
readonly sexy: string;
// [成员函数]
init() {
this.occupation = 'Engineer';
// this.sexy = ''; error
}
constructor() {
this.age = 18;
this.sexy = '男';
this.init();
}
/**
* [存取器]
* get/set 必须有相同的可访问属性
* 提供一层额外的访问控制
*/
private _money: number = 100;
get money(): number = {
return this._money;
}
set money(value: number) {
if (value > 0) {
this._money = value;
}
}
/**
* [索引成员]
* 不允许定义可访问修饰符
*/
[prop: number]: boolean;
}
let o = new C();
o.money = 1;
o[1111] = false;
- 类成员可访问性
class A {
public a: string = 'a'; // 默认,类内部,类外部,派生类内部,派生外部
protected b: stirng = 'b'; // 类内部,派生类内部
private c: string = 'c'; // 类内部
#r: string = 'r'; // 私有字段-私有属性新的表达式
init() {
this.a;
this.b;
this.c;
this.#r;
}
}
class Aa extends A {
init() {
this.a;
this.b;
this.c; //error
this.#r; //error
}
}
let o = new A();
this.a;
this.b; //error
this.c; //error
this.#r; //error
let oo = new Aa();
this.a;
this.b; //error
this.c; //error
this.#r; //error
- 构造函数
class A {
radius: number;
/**
* public 默认公有
* private 私有属性访问:不允许再类外部实例化
* protected 受保护,只允许在内部,和派生内部
*/
public constructor(r: number, x: string, y: number); // 重载构造函数
public constructor(r: number, x: boolean, y: number); // 重载构造函数
// 构造函数没有返回值,因为返回对象实例
public constructor(r: number, x: string | boolean, public y: number) { // 如果参数带属性修饰符,则会申明为类的成员变量
this.radius = r;
}
init() {
return new A(123, 'x', 1); // public protected private
}
}
class B extends A {
init() {
return new A(123, 'x', 1); // public protected
}
}
let a = new A(123, 'x', 1); // public
console.log(a.y) // 1
- 继承
class A {
public x: number = 1;
protected y: number;
private z: number;
color: 'red' | 'yellow' | 'blue';
constructor() {
this.y = 2;
this.z = 3;
this.color = 'red';
}
init() {
console.log('A zhongde init');
}
}
class B extends A {
public x: number = 2; //重写同名属性
// private y:number = 3 error 只允许放宽基类的可访问性,不允许缩小
public y: number = 3;
// color:'red' | 'yellow' | 'blue' | 'xxxx' error 重写基类成员要保证兼容性,不能比父类范围小
init() { //重写父类同名方法
console.log('B 中的 init');
console.log(this.x); // 2 重写同名属性
console.log(super.x); // udnefined 同名属性不能用super访问
}
constructor() {
// this 在派生构造函数中,this必须在super()之后使用
super(); // 派生类的构造函数必须用super实现父类构造函数
this.init();
this.color = 'red';
}
}
let b = new B();
- 实例化派生类的初始化顺序
- 初始化基类属性
- 调用基类构造函数
- 初始化派生类属性
- 调用派生类构造函数
- 接口继承类 [仅支持单继承, 不允许多继承, 只能指定一个基类]
class A {
a: string = '';
b(): boolean {
return true;
}
}
interface B extends A {};
// <=>
/**
* {
* a:string,
* b():boolean
* }
*
*/
const b: B = {
a: '123',
b(): boolean {
return false
}
}
// 如果继承非共有成员,那么接口的实现必须由基类或者基类的子类
class C {
private x: boolean = true;
protected y: string = '1';
}
interface cc extends C {}
class D extends C implements cc {
protected y: string = '3';
}
- 类实现接口
// 一个类可以实现多个接口
// 接口定义的成员没有修饰符,所以类继承的时候都是public
interface Cc {
color: string
}
interface P {
area(): number
}
class CC implements Cc, P {
color: string = 'q';
area(): number {
return 1
}
}
- 静态成员
class F {
public static a: string = 'a';
protected static b: string = 'b';
private static c: string = 'c';
a() {
F.a;
F.b;
F.c;
}
}
F.a;
// F.b; error
// F.c; error
class E extends F {
a() {
F.a;
F.b;
// F.c;error
}
}
E.a;
// E.b;error
// E.c;error
- 抽象类与抽象成员
abstract class Base {
abstract a: string; // 抽象成员不包含具体实现代码
abstract b: boolean;
}
abstract class Fase extends Base { //抽象类可以继承抽象类
abstract a: string;
// private abstract c:boolean; error 抽象成员不能申明为 private
b: boolean = false // 抽象类中的成员可以是抽象成员也可以不是
}
class Ease extends Fase {
a: string = 'a'; //继承抽象类必须实现所有抽象成员
c: boolean = false;
}
// let aa = new Fase() error 抽象类不能被实例化
- this类
class Co {
private count: number = 0;
add(): this {
this.count++;
return this
}
sub(): this {
this.count--;
return this
}
}
let ct = new Co();
ct.add().sub() //返回this类型可以链式调用
// this类型为动态的,即使继承了父类的方法返回的this,也是本身的实例
class Bo extends Co {}
let bt = new Bo();
bt.add() // 返回的是 bt实例,类型为Bo
- 类类型
class Lo {
static a: string = 'a';
c: boolean = false;
area(): number {
return 1;
}
}
interface k {
c: boolean,
area(): number,
}
let xx: Lo = new Lo(); // 类类型就是类的实例的类型
let yy: k = new Lo(); // 本质上就相当于实现了一个对象类型接口
}
- 类的构造函数类型
// 类本质上是一个构造函数
interface Rr {
new(): Lo,
a: string
}
const r: Rr = Lo; // Rr 为类的构造函数类型
特性划分
单元类型
- 仅仅包含一个可能值的类型
- undefined 类型
- null 类型
- unique symbol 类型
- void 类型
- 字面量类型
- 联合枚举成员类型
字面量类型
- 每一个字面量类型都有一个可能的值就是自己本身
TS复合类型介绍
- 具有更丰富表达的更高级的类型,包括一些类的运算(类参数,作用域,运算符,等)
泛型 [关键词<T>]
泛型参数基本定义
/**
* 泛型,可选类型参数,形式类型参数列表,默认类型参数
* 如果有两个形式类型参数那么函数的参数至少有两个
* T的泛型约束是 string,T的参数类型一定是基约束[string]的子类型
* 如果没有申明约束默认是any类型
*/
function identify < T extends string, U = T, F = boolean > (a: T, b: U, c: F): T | U {
return c ? a : b;
}
let a = identify <'1'> ('1', '1', false);
泛型函数
// 调用签名
<T>(x:T):T
// 构造签名
new <T>():T[]
// 泛型函数类型推断
function f<T>(x:T):T{return x;}
/**
* 非必要不放宽
* 编译器会尽可能细化类型
*/
f<string>('a'); // => 推断返回类型为 'a'
// 注意事项
// 泛型函数的泛型参数本质是描述参数与参数之间,参数与返回值之间的类型关联
// 如果泛型参数除本身申明以外只在函数签名中出现了一次,那就说明这个泛型类型参数可有可无
// 非必要不使用泛型类型参数
泛型接口
interface Array<T>{
pop():T|undefined,
[n:number]:T
// ....
}
泛型别名
type T0<T> = {
right:T0<T> | null,
left:T0<T> | null,
value:T
}
let tree:T0<number> = {
right:{
right:null,
left:null,
value:0
},
left:null,
value:1
}
泛型类
interface A<T>{
a:T
}
class Base<T>{
b?:T
}
class C<T> extends Base<T> implements A<T>{
constructor(public readonly a:T){
super();
}
}
局部类型
function f<T>(x:boolean){
if(x){
interface T{a:number} //局部作用域,两个T不影响
const x:T = {a:1};
}else{
interface T{a:string};
const x:T = {a:""};
}
interface Tt<T> {t:T};// 可用外部泛型T
}
联合类型 [关键词 '|']
/**
* 联合类型:既是也是
* 类型成员为交集
* 同名类型成员返回值为并集
* 可选属性与非可选属性为可选属性
* 小范围|大范围=>大范围
* 相同列表参数的调用签名/构造签名返回值并集
* 同名函数类型成员=>参数成员列表并集,同位参数类型交集,返回值并集
*/
interface c{
a:string,
b:number,
c:boolean,
[x:number]:boolean,
(x:number):number[]
aaa(y:boolean,z:bigint):number
}
interface t{
a:bigint,
d:null,
e:number[],
[x:number]:undefined,
(x:number):string[];
aaa(x:number):string
}
type T = c|t;
let x:T;
// x.a //公共属性 => string | bigint
// x.b error
// x.c error
// x.d error
// x.e error
// x[1] => undefined | boolean
// x() => string[] | number[]
// x.g => number | string 可选
// x.aaa => aaa(arg0: never, z: bigint): string | number
交叉类型 [关键词 &]
/**
* 交叉类型:既是又是
* 类型成员为并集
* 同名类型成员返回值为交集
* 相同列表参数的调用签名/构造签名=>重载 要注意重載順序
* 小范围&大范围=>小范围
*/
interface c{
a:string,
b:number,
c:boolean,
[x:number]:boolean,
(x:number):number,
g?:string,
aaa(y:boolean,z:bigint):number
}
interface t{
a:bigint,
d:null,
[x:number]:undefined,
(x:string):boolean;
g:number,
aaa(x:number):string
aaa(x:bigint):boolean
}
type T = c&t;
let x:T;
// x.a //公共属性为交集 => never
// x.b //类型成员为并集 => number
// x.c //boolean
// x.d //null
// x[1] //=> never
// x.aaa //=> 重载
// x.g //=> number&string=>never 必选
联合类型与交叉类型运算 [关键词 分配律]
a&b|c&d <=> (a&b)|(c&d) //相当于乘法和和加法,可以使用分配律
never | 0 => 0 // never是任何类型的子类型,并集可消去
索引类型 [关键字 keyof]
interface Point{
x:number,
y:number,
[p:number]:boolean
}
interface B{[p:string]:1};
interface C{[p:number]:1};
// 索引类型查询
/**
* 对象,接口都有索引
* 所以引入索引类型查询
* 索引类型只可能是 string number symbol 的联合类型
* 联合类型的索引类型查询为交集=>参考联合类型成员计算
* 交叉类型的索引类型查询为并集=>参考交叉类型成员计算
* 如果索引为字符串索引,那么索引查询类型为 string | number
* 如果索引为数值索引,那么索引查寻类型为 number
*/
type T = keyof Point; // 'x'|'y'|number
type T1 = keyof ({a:number,b:number}&{c:string,b:string});// 'a'|'b'|'c'
type T2 = keyof {a:1,b:2} // 'a'|'b'
type T3 = keyof any; // string | number | symbol
type T4 = keyof unknown; // never 这个比较特殊
type T8 = keyof B; // string | number
type T9 = keyof C; // number
// 索引访问类型
/**
* 通过索引类型获取索引成员
* 同名属性拥有更高的优先级
*/
type T5 = Point[T]; // number | boolean
type T6 = Point['x']; // number
interface P {
a:true;
[p:string]:boolean;
}
type Ta = P['a']; // true
type Tb = P['b']; // boolean
// 索引类型的应用
/**
* 实现安全的对象属性访问
* 在对象属性不存在时会产生编译错误
* 避免 a.x === undefined 的情况出现
*/
function getProperty<T,K extends keyof T>(obj:T,key:K):T[K]{
return obj[key];
}
function xx(p:Point){
// 如果没有显示传入参数类型,泛型函数会自己推断类型,会比显示传入类型更加精确
return getProperty(p,'cc'); // error 类型 ‘cc’ 的参数不能赋值给‘x’|‘y’的参数
}
映射对象类型 [关键词 in]
- 映射对象类型是什么
- 用于操作类型的运算符 {[P in K]:T}
- 本质是遍历一个联合类型,将联合类型的各个类型成员作为新的对象类型的各个属性
- 由于 string|number|symbol 才能作为键名,所以K必须能赋值给这些类型
- 映射对象为什么会出现
- 定义对象类型的扩展
- 映射对象的应用
// 映射对象类型in+索引对象查询keyof+索引访问类型[]=> 映射对象类型
type T = { a: number, b: string };
// 非同态对象类型映射,可加各种修饰符,值的限定
type M = { readonly [P in keyof T]?: T[P] };// readonly ? 修饰符都可选
// 同态对象类型映射, 映射对象保留属性修饰符
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
// 改进的修饰符拷贝=>添加或者删除修饰符(原来只能添加,不可删除)
type C = { +readonly [P in keyof T]-?: T[P] } // 添加只读属性,减去必选属性
// 同态对象映射默认保留修饰符(是类型原有的修饰符,不是属性修饰符)
type V = readonly number [];
type Ho<T> = {[P in keyof T]:T[P]};
let a:Ho<V> = [1,2,3];
a[0] = 2; // error 默认也是只读
条件类型 [关键词 ?]
// 条件类型 :根据条件判断返回类型
type TypeName<T> = T extends string
? 'stirng'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: never;
// 裸类型参数:没有任何装饰的类型参数
type T0<T> = T extends string ? true : false; // 裸类型参数
type T1<T> = [T] extends [string] ? true : false; // 非裸类型参数
// 分布式行为:如果实参为联合类型,会将条件类型展开为子类型构成的·联合类型
type T3 = A | B;
type R1 = T3 extends K ? X : Y;
// <=>
type R1 = (A extends K ? X : Y) | (B extends K ? X : Y);
// 分布式行为的应用:过滤联合类型
type Exclude<T,U> = T extends U ?never:T;
type C = Exclude<string|undefined,null|undefined>
= (string extends null|undefined ? never : string)
| (undefined extends null|undefined ? never : undefined)
= string | never
= string;
// 避免分布式行为:利用裸类型参数
type CT<T> = T extends string ? true :false; // 具有分布式行为
type T = CT<string|number>; // => true | false = boolean
type CP<T> = [T] extends [string] ? true :false; // 避免分布式行为,将裸类型参数修改为非裸类型参数
type T = CT<string|number>; // => false
类型推断 [关键字 infer]
- 是什么
- infer 是用在条件类型上的类型修饰符(并且该类型只能在true分支出现)
- 为什么会出现
- 需要推断类型,推断的是输入的类型T中包含的类型
- 用法
type R<T> = T extends Array<infer U>?U:never;
- 用法扩展
type M<T> = T extends { a: infer U, b: infer U } ? U : never;
type rr = M<{ a: string, b: number }>;//string | number
内置工具类
- 根据输入的类型返回一个新的类型
- lib/lib.es5.d.ts
类型查询 [关关键字 typeof]
- TS在JS的 typeof 操作符上进行了扩展,
- JS上的 typeof 能够获取值的类型,而TS扩展后的 typeof 能够在类型表达式使用
- 注意 typeof 在类型表达式中的用法和值表达式中的用法的区别
let a:{x:string} = {x:'1111'};
type T = typeof a;
if(typeof a === 'object'){
console.log(11111);
}
类型断言 [关键字 as <T>]
作用
- 开发者比编译器更清楚类型
- 使用场景
class P{
x:number =1;
y:string = '';
}
class C extends P {
x:number = 2;
y:string = '';
z:boolean = false;
}
function getP(t:string):P{
if(t === 'p')return new P();
return new C();
}
let x = getP('c');
x.z;// Property 'z' does not exist on type 'P'
// 解决方法,类型断言
(<C>x).z;
(x as C).z
- 类型断言的约束
- exp as T
- 必选满足 exp类型 能赋值给T类型,或者 T类型 能赋值给exp类型
// 虽然有约束,也可以有变通方法 ,利用顶端类型
const a = 1 as string; // error
const a = 1 as unknown as string;
console.log(typeof a,a); // number 1
const x:typeof a = '1'; // 在类型表达式中,typeof a 就为 string了
- 其他断言符号
// const断言,转为不可变类型
let a = 0 as const;
let x = [1,2] as const; // 如果为对象或者数字,则转变为只读
//非空断言,
let a:(string | undefined)
let x = a.length; // error 编译器无法识别是string,undefined
let y = a!.length;
类型细化
是什么
- TS能识别的代码解构进行-类型细化
- 类型细化的本质:
- 我们可以在类型表达式中推断出类型
- 但如何根据值的表达式中推断出类型的范围
- 所以需要类型的细化
- 这些工作也是编译器可以识别的
方式
- 类型守卫
// typrof 获得操作数的数据类型
function f(p: unknown) {
if (typeof p === 'string') {
// string
}
}
// instanceof 实例对象与构造函数之间的关系
class A { x: number = 1; };
class B { y: number = 2 };
function f0(p: A | B) {
if (p instanceof A) {
// A
}
}
// in 判断原型链中是否存在某属性
function f1(x: A | B) {
if ('x' in x) {
// A
}
}
// 逻辑与或非,与等式类型守卫
function f2(p: null | undefined | number) {
if (p) {
// number
} else if (p === 0) {
// 0
} else {
// null/undefined
p = 12 // 如果是用了逻辑类型守卫,又对变量赋值,则该类似守卫无效
console.log(1223)
}
}
// 自定义类型守卫函数,本质就是返回一个boolean的函数
function isTypeB(p: A | B): p is B { // 就是返回一个boolean
return (p as B).y !== undefined;
}
function f3(x: A | B) {
if (isTypeB(x)) {
// B
}
}
// this 类型守卫,只能用于函数或者方法,不能用于属性或者存取器
class Student {
isStudent(): this is Student {
return true
}
}
- 可识别联合类型
interface C { type: 'C' };
interface D { type: 'D' };
function f4(x: C | D) {
if (x.type === 'C') {
// C
}
}
- 赋值语句
let x = 1; // => x number
let y: typeof x = '' // error number
- 控制流语句
function f5(c: boolean) { //分析可能的执行路径
let x: number;
x// error 赋值前使用
if (c) {
x = 1;
x;// number
}
}
- 断言函数 =>本质与 自定义类型守卫函数 一样,只是更加语义化
function f6(x:unknown):asserts x{
if(!x){
throw new Error("x 为假")
}
}
function f7(x:unknown):asserts x is number{
if(typeof x !== 'number'){
throw new TypeError('不为 number')
}
}