经过前两篇文章《TypeScript入门教程学习》和《TypeScript类型进阶》两篇文章已掌握TS过半的知识点了。本篇文章将介绍TS中一些相对高级一些的类型,并对类型兼容性进行深入阐述,最后补充一些比较零碎的TS知识点,计划将除类型编程(包括映射类型和infer类型)以外的知识点都讲完。
TypeScript
选择结构性类型子系统?带着问题开始本篇文章的阅读吧
我们知道即使不显式的定义类型,也能够自动做出类型推论,这背后就隐藏着TS的类型推论。
ts// 不显式的定义类型,也能够自动做出类型推论
let age = 1 // let age: number
// 声明未赋值 自动推断为any类型
let person; // let person: any
var arr = ['zhangsan', 13]; // arr: (number| string)[]
// 上下文类型 (开启 strict模式)
// mouseEvent 自动推论为 MouseEvent
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.test); //<- Error
};
语法 值 as 类型
用途
unknow
必须配合 as
使用any
any
可能掩盖真正的类型错误as any
,另一方面也不要完全否定它的作用,需要平衡any
断言为一个具体的类型类型断言在类型编程的一个小技巧
tsinterface A {
name: string;
age: number;
sex: boolean;
}
interface B {
age: number;
}
// 我想从A类型中踢出B类型的key怎么办?
type MinusKey<A extends object, B extends object> = {
[P in Exclude<keyof A, keyof B>]: A[P]
}
type AB = MinusKey<A, B>;
// 还有 其他方案吗?
type AB2 = {
[P in keyof A as P extends keyof B ? never : P]: A[P]
}
// 如果我是想获得key类型的联合类型呢?
type Val = {
[P in keyof A]: P extends keyof B ? never : P
}[keyof A]; // "name" | "sex"
any
,any
类型可以断言为任何类型tsinterface Ani {
name: string;
}
interface Person extends Ani {
age: number;
}
interface Student extends Person {
school: string;
}
let ani: Ani = {name: '熊猫'}
let stu: Student = {
name: 'zs',
age: 23,
school: '清华'
}
let stu2: Student = ani as Student;
let ani2: Ani = stu as Ani;
tsfunction getCacheData<T>(key: string): T {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData<Cat>('tom'); // tom: Cat
const
断言是 TypeScript 3.4 中引入的一个实用功能。在 TypeScript 中使用 as const
时,可以将对象的属性或数组的元素设置为只读,向语言表明表达式中的类型不会被扩大。
ts// const arr = [3, 4]; // arr: number[]
const arr = [3, 4] as const; // arr: readonly [3, 4]
// arr.push(5) // TS报错,不允许
在类型编程中,const
的使用还会起到神奇的作用,比如
由于这个案例比较复杂,感兴趣的还是看原文吧 《一道3层的TypeScript面试题》
undefined
和null
类型tsconst prepareValue = (value?: string) => {
// ...
parseValue(value!); // 使用非空断言要非常小心
};
// 非空断言常用于搜索内容
const arr = ['zs', 'ls'];
const item = arr.find(it => it === 'ls'); // item: string | undefined
const item2 = arr.find(it => it === 'ls')!; // item2: string
tsclass Class1 {
public age = 18;
constructor() {}
}
class Class2 {
public name = "TypeScript";
constructor() {}
}
function logClassInfo(inst: Class1 | Class2) {
if (typeof (inst as Class1).age === 'number') {
console.log((inst as Class1).age);
} else {
// console.log(inst.name); // 报错不能确定inst是 Class2类型
console.log((inst as Class2).name);
}
}
// 上述案例比较冗肿, 使用 instanceof 类型保护优化
function logClassInfo2(inst: Class1 | Class2) {
if (inst instanceof Class1) {
console.log(inst.age);
} else { // 排除了 Class1 只剩下 Class2
console.log(inst.name);
}
}
typeof
能确定除null
以外的基本类型和function
类型
tsfunction test(x: string | number) {
if (typeof x == "string") {
// number类型没有length属性,这里已经识别x是string类型
return x.length + " is a str";
}
if (typeof x == "number") {
return x * 10; // 这里正常,说明x已被识别为number类型
}
}
tsinterface Ani {
name: string
}
interface Person extends Ani {
family: string[]
}
function getMember(obj: Ani | Person) {
if('family' in obj) {
// 如果前面没有in 后面一行会提示TS错误
return obj.family.join(', ');
}
return obj.name
}
tsfunction getVal(arg: number | 'name' | 'age') {
if (['name', 'age'].includes(arg as string)) {
console.log(arg.length); // TS报错
} else {
console.log(arg.toFixed());// TS报错
}
}
function isString(value: number | 'name' | 'age'): value is 'name' | 'age' {
return ['name', 'age'].includes(value as string);
}
function getVal2(arg: number | 'name' | 'age') {
if (isString(arg)) {
console.log(arg.length); // ok
} else {
console.log(arg.toFixed()); // ok
}
}
这个小知识点,不知道放哪里合适,暂且放到这里吧
Triangle
到 Shape
,我们同时还需要更新 area函数。tsconst enum ShapeKind {
Circle,
Square,
}
interface Square {
kind: ShapeKind.Square;
size: number;
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
type Shape = Square | Circle;
function area(s: Shape): number { // switch语句不能少,否则这一行TS报错
switch (s.kind) {
case ShapeKind.Square:
return s.size * s.size;
case ShapeKind.Circle:
return Math.PI * s.radius ** 2;
}
}
const re = area({kind: ShapeKind.Circle, radius: 2})
前面TS入门一文简单介绍过函数重载,这里再深入研究下
ts// 重载签名
function sum(a: number, b:number): number;
function sum(a: string, b:string): string;
// 实现签名
function sum(a: number | string, b: number | string): number| string {
if(typeof a === 'string') {
return a + b
}
return a + (b as typeof a);
}
let result = sum(1, 3); // result: number
let result2 = sum('b', 'c'); // result2: string
sum('b', 2); // 报错 没有对应的重载签名
函数重载或方法重载有以下几个优势
与函数重载类似
与函数重载类似
所谓的类型兼容性用于确定一个类型是否能赋值给其他类型。TypeScript中的类型兼容性是基于结构类型的,其基本原则是,如果x要兼容y,那么y至少要具有与x相同的属性。
tsinterface Person {
name: string;
}
interface Animal {
name: string;
}
let animal: Animal = { name: '熊猫' };
let person: Person = animal; // 允许这样赋值,因为TS是结构性类型子系统
Java
或C
使用基于名义类型的系统 (也叫类类型),严格的检测继承关系TypeScript
使用基于结构性的类型系统 ,也被称为“鸭子类型”,像鸭子一样会游泳会“嘎嘎叫”的动物就是鸭子javaScript
选择结构性类型子系统?
JavaScript
里广泛地使用匿名对象,例如函数表达式和对象字面量,所以使用结构类型系统来描述这些类型比使用名义类型系统更好。any
类型可以赋值给除了never
任意其它类型
any
never
类型可以赋值给其它任意类型,但其它类型不能赋值给never
unknow
和 never
同为顶级类型,其它类型都可以赋值给unknow
类型,但unknow
赋值给其它类型(不包括any
)时要断言为具体类型以 函数fa
和函数fb
举例,
如果想让fb = fa
成立。
ts// fa: (a:number) => number;
let fa = (a: number) => 0;
// fb: (b: number, c: string) => number
let fb = (b: number, c: string) => 0;
// 如果将函数fb赋值给函数fa,那么要求fa中的每个参数都应该在fb中有对应,也就是fa的参数个数小于等于fb的参数个数
fb = fa; // 👌
fa = fb; // not ok 函数fb的参数数量大于fa函数的参数数量
// fb2的参数类型与fb要对应 (同一个参数前者参数类型是后者的子集也可以)
let fb2 = (b: 2 | 3, c: string) => 0;
fb = fb2;
// 按照前个参数总结, 先不考虑返回值,无参数的函数可以赋值给任意函数
fb = () => 0;
// 可选参数, 如果有就校验参数类型, 没有就不校验这个参数
let fc:(arg: number, arg2?: number) => number
= (num1: number, num2: number) => 0
// 剩余参数意味着是任意个可选参数, 校验同可选参数校验
let fd: (...args: number[]) => number;
fd = (arg: number, arg2: number) => 0
fc = () => 0;
tslet funcA = function(arg: number | string): void {};
let funcB = function(arg: number): void {};
// funcA = funcB 和 funcB = funcA都可以
tsfunction merge(arg1: number, arg2: number): number; // merge函数重载的一部分
function merge(arg1: string, arg2: string): string; // merge函数重载的一部分
function merge(arg1: any, arg2: any) { // merge函数实体
return arg1 + arg2;
}
function sum(arg1: number, arg2: number): number; // sum函数重载的一部分
function sum(arg1: any, arg2: any): any { // sum函数实体
return arg1 + arg2;
}
let func = merge;
func = sum; // error 不能将类型“(arg1: number, arg2: number) => number”分配给类型“{ (arg1: number, arg2: number): number; (arg1: string, arg2: string): string; }”
tsenum Status {
On,
Off,
}
let s = Status.On;
s = 1; // ok
s = 3; // error 只能是 1或2
tsenum Status {
On, // Status.On = 0
}
enum Color {
White, // Color.White = 0
}
let s = Status.On;
s = Color.White; // 不能将类型“Color.White”分配给类型“Status”。
tsenum Status {
On = "on",
Off = "off",
}
let s = Status.On;
s = "off"; // error
tsclass Animal {}
class People {
static age: number;
constructor(public name: string) {}
static getAge: () => number;
sayHello(to: string) {}
}
class Food {
constructor(public num: number, public name: string) {}
howToCook(to: string) {}
// sayHello(to: number) {} // 方法的检测与函数的检测规则一样
// sayHello(to: string) {}
}
let a: Animal = new Animal();
let p: People = new People('zs');
let f: Food = new Food(1, '米饭');
a = p; // ok 类的静态属性和方法及构造函数不会参与比较
a = f; // oK 类的实例属性和方法比较 遵循结构性继承关系, 即f是a的子类
p = f; // error f中没有sayHello方法
tsclass Parent {
private age: number;
// protected age: number; // 换成protected也不行
}
class Children extends Parent {
}
class Other {
private age: number;
}
const children: Parent = new Children();
const other: Parent = new Other(); // 不能将类型“Other”分配给类型“Parent”。类型具有私有属性“age”的单独声明
typeof
可以获得类的静态类型, 静态类型的比较与实例类型比较规则类似,此时会比较构造函数tsclass Animal {}
class People {
static age: number;
// constructor(public name: string) {} // 构造器也会参与比较
static getAge: () => number;
sayHello(to: string) {}
}
var aniClass: typeof Animal = {} as any as typeof Animal;
var peopClass: typeof People = {} as any as typeof People;
aniClass = peopClass // ok
peopClass = aniClass // error aniClass 没有静态属性age 和静态方法 getAge
tsinterface Data<T> {}
let data1: Data<number> = { name: "zs" };
let data2: Data<string> = {};
data1 = data2; // ✅ 因为推断的结果结构一样
let dat1: Data2<number> = {data: 1};
let dat2: Data2<string> = {data: 'str'};
dat1 = dat2; // error 推断的结果不一样
如果定义了两个相同名字的函数、接口或类,那么他们会合并成一个类型
tsfunction reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
tsinterface Alarm {
price: number;
}
interface Alarm {
weight: number;
}
// 等价于
interface Alarm {
price: number;
weight: number;
}
interface Alarm {
price: string; // 类型不一致,会报错
}
interface Alarm {
info: {
a: number;
}
}
interface Alarm {
info: {
b: string; // 报错 不存在深度合并
}
}
关键字 | JS中含义 | TS中含义 |
---|---|---|
void | 忽略返回值 或返回值是 undefined | 表示一种类型undefined 是它的子集区别是在一些高级回调中兼容不同类型的转化 |
typeof | 判断基本类型和function类型,不包括null 具有类型保护的作用 | 获取JS对象的类型,获取类的类型 |
class | 变量声明的一种方式 | 既是声明变量也是定义一种类型 |
instaceof | 一种运算符 检测左边是否是右边的实例 | 具有类型保护的作用 |
in | 检测属性 | 具有类型保护的作用 |
! | 转换为boolean 值 | 非空断言 |
new | 使用构造函数创建对象 | 定义构造函数类型 |
this
来表示this
的类型,这个虚拟参数放在函数参数的第一个位置。this
类型仅在类或接口的非静态成员中可用 (通过es5的方式定义类, TS的支持不友好)this
用来给三方库的使用补充TS描述class
中构造器函数默认返回this
--noImplicitThis
更精确的检查this
ts// 1. 对于es5的通过function定义类TS支持不优好,这里不好举例
// 2. this 用来给三方库的使用补充TS描述
declare interface Lib {
name: string;
sayHello: (this: Lib) => Lib;
}
interface Window {
myLib: Lib;
}
const resp = window.myLib.sayHello();
// 3
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
return function() {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
// 这里的this 并不是deck, 实际可能是 window(非严格模式)或者undefined(严格模式下)
// 需要配置文件开启 -- noImplicitThis 才会提示错误
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
},
createCardPicker2: function(this: typeof deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker2();
let pickedCard = cardPicker();
如果您已经熟悉了this的指向(默认绑定、隐式绑定、显示绑定、new绑定)
问题,那么TS中this
没有什么知识点要说了。
object
表示非原始类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。object
类型,就可以更好的表示像Object.create
这样的API。例如tsdeclare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
// create(42); // Error
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!