摸鱼偷学TS:高级类型浅聊一下 在前面的内容中,我们学习了一些基础类型,其实TypeScript中还有一些高级类型,本篇
浅聊一下
高级类型
交叉类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
interface IAnyObject {
[prop:string]:any;
}
function mixin<T extends IAnyObject,U extends IAnyObject>(first:T,second:U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = first[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = second[id];
}
}
return result;
}
const obj = mixin({a:'juejue'},{b:666});
console.log(obj);//{ a: 'juejue', b: 666 }
- 创建一个空对象
result
,并通过类型断言<T & U>{}
将其指定为T & U
类型。这告诉 TypeScript 编译器,虽然result
最初是空对象,但我们打算将其填充为T & U
类型的对象。 - 通过第一个循环,将
first
对象的所有属性复制到result
对象中。这里使用了类型断言<any>result
来避免 TypeScript 编译器对属性赋值的严格检查,因为我们知道这样的操作是安全的。 - 第二个循环遍历
second
对象的属性。如果result
对象还没有这个属性(通过hasOwnProperty
检查),则将该属性从second
复制到result
。这确保了second
对象的属性不会覆盖first
对象中已存在的同名属性
看到结果我们可以发现返回的对象拥有着两个对象所有的属性
联合类型
如果你希望一个变量的值是多种类型之一,那么你可以使用联合类型
联合类型表示一个值可以是几种类型之一,我们用竖线(|
)分隔每个类型,所以number | string | boolean
表示一个值可以是number
、string
、或boolean
function format(args: string[] | string){
let res = '';
if(typeof args === 'string'){
res = args.trim();
}
else{
res = args.join(' ').trim();
}
console.log(res);
}
format(['a', 'b']); // a b
format('a b'); // a b
无论我们是传入字符串数组还是字符串都可以很好地调用formate
方法
类型别名
类型别名会给一个类型起个新名字,类型别名有时和接口很像,但是可以作用于原始值、联合类型、元组以及其它任何你需要手写的类型.
你可以使用 type SomeName = someValidTypeAnnotation
的语法来创建类型别名
type some = boolean | string
const b: some = true // ok
const c: some = 'hello' // ok
const d: some = 123 // 不能将类型“123”分配给类型“some”
此类型别名也可以是泛型
type Container<T> = {value: T} | {name: string,age : T}
// let o: Container<number> = {value:1}
let o: Container<number> = {name:"juejiue",age:1}
看到这里,发现类型别名怎么跟interface这么像呢?怎么来区别一下?
interface
只能用于定义对象类型,而 type
的声明方式除了对象之外还可以定义交叉、联合、原始类型等,类型声明的方式适用范围显然更加广泛
可辨识联合类型
在开始之前我们要先搞清楚两个概念「类型字面量」与「字面量类型」,因为会在可辨识联合类型的学习中用到类型字面量的特性
字面量类型
字面量(Literal Type
)主要分为 真值字面量类型(boolean literal types
)、数字字面量类型(numeric literal types
)、枚举字面量类型(enum literal types
)、大整数字面量类型(bigInt literal types
)和字符串字面量类型(string literal types
const h1: 123 = 123;
const h2: "123" = "123";
const h3: true = true;
const h4: 0b11 = 3;
字面量类型的值要和实际的值的字面量要一一对应,如果不一致就会报错
当字面量类型与联合类型结合
type T1 = 'North' | 'East' | 'South' | 'West';
function move(distance: number, direction: T1) {
...
}
move(10, 'North');
此时第二个参数direction就只能传入 'North' | 'East' | 'South' | 'West'
其中的一个值
类型字面量
类型字面量不同于字面量类型
type Foo = {
baz: [
number,
'xiaomuzhu'
];
toString(): string;
readonly [Symbol.iterator]: 'github';
0x1: 'foo';
"bar": 12n;
};
这不是和接口一模一样吗?有点相似,在一定程度上类型字面量可以代替接口
可辨识联合类型
先来假设一个场景,有两个功能,一个是添加用户,一个是删除用户,添加用户不需要id字段,因为会自动生成,而删除用户需要id字段,于是有以下这段代码
interface Info {
username: string;
}
interface UserAction {
id?: number;
action: 'create' | 'delete';
info: Info;
}
先来创建用户
const action: UserAction = {
id: 10,
action: 'create',
info: { username: 'juejue' }
}
我们发现当我们创建用户并且传入一个id时是合法的,但是我们明明不需要这个id,如何解决?
type UserAction = {
id: number
action: 'delete'
info: Info
} |
{
action: 'create'
info: Info
}
const action: UserAction = {
// id: 10,
action: 'create',
info: { username: 'juejue' }
}
在这里使用类型字面量来解决,如果在创建用户时仍然传入id
似乎完美解决问题,但是如果碰上以下这种情况:
const UserReducer = (userAction: UserAction) => {
console.log(userAction.id);
}
我们不清楚是否具有id属性,这里就会报错了
const UserReducer = (user: UserAction) => {
switch (user.action) {
case 'delete':
console.log(user.id);
break;
default:
break;
}
}
可以使用类型守卫来判断
我们上面提到了 user.action
就是辨识的关键,被称为可辨识的标签,我们发现上面这种模式要想实现必须要三个要素:
- 具有普通的单例类型属性—可辨识的特征,上文中就是
delete
与create
两个有唯一性的字符串字面量 - 一个类型别名包含联合类型
- 类型守卫的特性,比如我们必须用
if switch
来判断user.action
是属于哪个类型作用域即delete
与create
结尾
知识点有点复杂,得细心看看...
转载自:https://juejin.cn/post/7373306258404556809