十一、类型推论与类型别名
类型推论
// 当我声明了一个变量并没有定义类型,TypeScript会在没有指定类型时自己推导出一个类型
let str = "str";
// 上面的代码就会把变量 str 推导为 string 类型,这时,该变量是不能赋值为其它类型的对象
str = 123; // ❌
// 当我只声明变量,并没有进行赋值操作时,该变量会被推导为 any
let x;
// 这时,它可以被随意赋值
x = "213";
x = 123;
x = true;
类型别名
// type 关键字(可以给一个类型定义一个名字)多用于复合类型
// 定义类型别名
type str = string;
let s: str = "我是小满";
console.log(s);
// 定义函数别名
type str = () => string;
let s: str = () => "我是小满";
console.log(s);
// 定义联合类型别名
type str = string | number;
let s: str = 123;
let s2: str = "123";
console.log(s, s2);
// 定义值的别名
type value = boolean | 0 | "213";
let s: value = true;
//变量s的值 只能是上面value定义的值
type 和 interface 的区别
- interface 可以继承 type 只能通过 & 交叉类型合并
- type 可以定义 联合类型 和 可以使用一些操作符 interface 不行
- interface 遇到重名的会合并 type 不行
type 高级用法
// 左边的值会作为右边值的子类型遵循图中上下的包含关系
type a = 1 extends number ? 1 : 0; //1
type a = 1 extends Number ? 1 : 0; //1
type a = 1 extends Object ? 1 : 0; //1
type a = 1 extends any ? 1 : 0; //1
type a = 1 extends unknow ? 1 : 0; //1
type a = 1 extends never ? 1 : 0; //0
十二、never 类型
TypeScript 使用 never 类型来表示不应该存在的状态
// 返回never的函数必须存在无法达到的终点
// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
throw new Error(message);
}
// 因为存在死循环,所以 loop 将不会有返回值
function loop(): never {
while (true) {}
}
never 和 void 的差异
// void类型只是没有返回值 但本身不会出错
function Void(): void {
console.log();
}
// 只会抛出异常没有返回值
function Never(): never {
throw new Error("aaa");
}
// 当我们鼠标移上去的时候会发现 只有void和number never在联合类型中会被直接移除
type A = void | number | never;
never 类型的一个应用场景
举一个我们可能会见到的例子
type A = "小满" | "大满" | "超大满";
function isXiaoMan(value: A) {
switch (value) {
case "小满":
break;
case "大满":
break;
case "超大满":
break;
default:
//是用于场景兜底逻辑
const error: never = value;
return error;
}
}
比如新来了一个同事他新增了一个篮球,我们必须手动找到所有 switch 代码并处理,否则将有可能引入 BUG 。
而且这将是一个“隐蔽型”的 BUG,如果回归面不够广,很难发现此类 BUG。
那 TS 有没有办法帮助我们在类型检查阶段发现这个问题呢?
type A = "小满" | "大满" | "超大满" | "小小满";
function isXiaoMan(value: A) {
switch (value) {
case "小满":
break;
case "大满":
break;
case "超大满":
break;
default:
//是用于场景兜底逻辑
const error: never = value;
return error;
}
}
由于任何类型都不能赋值给 never 类型的变量,所以当存在进入 default 分支的可能性时,TS 的类型检查会及时帮我们发现这个问题
十三、Symbol 类型
自 ECMAScript 2015 起,symbol 成为了一种新的原生类型,就像 number 和 string 一样。 symbol 类型的值是通过 Symbol 构造函数创建的。
可以传递参做为唯一标识 只支持 string 和 number 类型的参数
let sym1 = Symbol();
let sym2 = Symbol("key"); // 可选的字符串key
Symbol 的值是唯一的
const s1 = Symbol();
const s2 = Symbol();
// s1 === s2 =>false
用作对象属性的键
let sym = Symbol();
let obj = {
[sym]: "value",
};
console.log(obj[sym]); // "value"
使用 symbol 定义的属性,是不能通过如下方式遍历拿到的
const symbol1 = Symbol("666");
const symbol2 = Symbol("777");
const obj1 = {
[symbol1]: "小满",
[symbol2]: "二蛋",
age: 19,
sex: "女",
};
// 1 for in 遍历
for (const key in obj1) {
// 注意在console看key,是不是没有遍历到symbol1
console.log(key);
}
// 2 Object.keys 遍历
Object.keys(obj1);
console.log(Object.keys(obj1));
// 3 getOwnPropertyNames
console.log(Object.getOwnPropertyNames(obj1));
// 4 JSON.stringfy
console.log(JSON.stringify(obj1));
如何拿到
// 1 拿到具体的symbol 属性,对象中有几个就会拿到几个
Object.getOwnPropertySymbols(obj1);
console.log(Object.getOwnPropertySymbols(obj1));
// 2 es6 的 Reflect 拿到对象的所有属性
Reflect.ownKeys(obj1);
console.log(Reflect.ownKeys(obj1));
Symbol.iterator 迭代器 和 生成器 for of支持遍历大部分类型迭代器 arr nodeList argumetns set map 等
var arr = [1, 2, 3, 4];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 3, done: false }
console.log(iterator.next()); //{ value: 4, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
测试用例
interface Item {
age: number;
name: string;
}
const array: Array<Item> = [
{ age: 123, name: "1" },
{ age: 123, name: "2" },
{ age: 123, name: "3" },
];
type mapTypes = string | number;
const map: Map<mapTypes, mapTypes> = new Map();
map.set("1", "王爷");
map.set("2", "陆北");
const obj = {
aaa: 123,
bbb: 456,
};
let set: Set<number> = new Set([1, 2, 3, 4, 5, 6]);
// let it:Iterator<Item> = array[Symbol.iterator]()
const gen = (erg: any): void => {
let it: Iterator<any> = erg[Symbol.iterator]();
let next: any = { done: false };
while (!next.done) {
next = it.next();
if (!next.done) {
console.log(next.value);
}
}
};
gen(array);
我们平时开发中不会手动调用 iterator 应为 他是有语法糖的就是 for of 记住 for of 是不能循环对象的应为对象没有 iterator
for (let value of map) {
console.log(value);
}
数组解构的原理其实也是调用迭代器的
var [a, b, c] = [1, 2, 3];
var x = [...xxxx];
那我们可以自己实现一个迭代器让对象支持for of
const obj = {
max: 5,
current: 0,
[Symbol.iterator]() {
return {
max: this.max,
current: this.current,
next() {
if (this.current == this.max) {
return {
value: undefined,
done: true,
};
} else {
return {
value: this.current++,
done: false,
};
}
},
};
},
};
console.log([...obj]);
for (let val of obj) {
console.log(val);
}
以下为这些 symbol 的列表:
Symbol.hasInstance 方法,会被 instanceof 运算符调用。构造器对象用来识别一个对象是否是其实例。
Symbol.isConcatSpreadable 布尔值,表示当在一个对象上调用 Array.prototype.concat 时,这个对象的数组元素是否可展开。
Symbol.iterator 方法,被 for-of 语句调用。返回对象的默认迭代器。
Symbol.match 方法,被 String.prototype.match 调用。正则表达式用来匹配字符串。
Symbol.replace 方法,被 String.prototype.replace 调用。正则表达式用来替换字符串中匹配的子串。
Symbol.search 方法,被 String.prototype.search 调用。正则表达式返回被匹配部分在字符串中的索引。
Symbol.species 函数值,为一个构造函数。用来创建派生对象。
Symbol.split 方法,被 String.prototype.split 调用。正则表达式来用分割字符串。
Symbol.toPrimitive 方法,被 ToPrimitive 抽象操作调用。把对象转换为相应的原始值。
Symbol.toStringTag 方法,被内置方法 Object.prototype.toString 调用。返回创建对象时默认的字符串描述。
Symbol.unscopables 对象,它自己拥有的属性会被 with 作用域排除在外
十四、泛型
函数泛型
我写了两个函数一个是数字类型的函数,另一个是字符串类型的函数,其实就是类型不同,
实现的功能是一样的,这时候我们就可以使用泛型来优化
function num(a: number, b: number): Array<number> {
return [a, b];
}
num(1, 2);
function str(a: string, b: string): Array<string> {
return [a, b];
}
str("独孤", "求败");
泛型优化
语法为函数名字后面跟一个<参数名> 参数名可以随便写 例如我这儿写了 T
当我们使用这个函数的时候把参数的类型传进去就可以了 (也就是动态类型)
function Add<T>(a: T, b: T): Array<T> {
return [a, b];
}
Add<number>(1, 2);
Add<string>("1", "2");
我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。
function Sub<T, U>(a: T, b: U): Array<T | U> {
const params: Array<T | U> = [a, b];
return params;
}
Sub<Boolean, number>(false, 1);
定义泛型接口
声明接口的时候 在名字后面加一个<参数>
使用的时候传递类型
interface MyInter<T> {
(arg: T): T;
}
function fn<T>(arg: T): T {
return arg;
}
let result: MyInter<number> = fn;
result(123);
泛型约束
我们期望在一个泛型的变量上面,获取其 length 参数,但是,有的数据类型是没有 length 属性的
function getLegnth<T>(arg: T) {
return arg.length;
}
这时候我们就可以使用泛型约束
于是,我们就得对使用的泛型进行约束,我们约束其为具有 length 属性的类型,这里我们会用到 interface,代码如下
interface Len {
length: number;
}
function getLegnth<T extends Len>(arg: T) {
return arg.length;
}
getLegnth<string>("123");
使用 keyof 约束对象
其中使用了 TS 泛型和泛型约束。首先定义了 T 类型并使用 extends 关键字继承 object 类型的子类型,然后使用 keyof 操作符获取 T 类型的所有键,它的返回 类型是联合 类型,最后利用 extends 关键字约束 K 类型必须为 keyof T 联合类型的子类型
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let o = { a: 1, b: 2, c: 3 };
prop(o, "a");
prop(o, "d"); //此时就会报错发现找不到
泛型类
声明方法跟函数类似名称后面定义<类型>
使用的时候确定类型 new Sub<number>()
class Sub<T> {
attr: T[] = [];
add(a: T): T[] {
return [a];
}
}
let s = new Sub<number>();
s.attr = [1, 2, 3];
s.add(123);
let str = new Sub<string>();
str.attr = ["1", "2", "3"];
str.add("123");