摸鱼偷学TS:高级类型

浅聊一下

在前面的内容中,我们学习了一些基础类型,其实TypeScript中还有一些高级类型,本篇文章就来总结一下TS中的高级类型

高级类型

交叉类型

交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

ts 复制代码
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表示一个值可以是numberstring、或boolean

ts 复制代码
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的语法来创建类型别名

ts 复制代码
type some = boolean | string
const b: some = true // ok
const c: some = 'hello' // ok
const d: some = 123 // 不能将类型"123"分配给类型"some"

此类型别名也可以是泛型

ts 复制代码
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

ts 复制代码
const h1: 123 = 123;
const h2: "123" = "123";
const h3: true = true;
const h4: 0b11 = 3;

字面量类型的值要和实际的值的字面量要一一对应,如果不一致就会报错

当字面量类型与联合类型结合

ts 复制代码
type T1 = 'North' | 'East' | 'South' | 'West';
function move(distance: number, direction: T1) {
    ...
}
move(10, 'North');

此时第二个参数direction就只能传入 'North' | 'East' | 'South' | 'West'其中的一个值

类型字面量

类型字面量不同于字面量类型

ts 复制代码
type Foo = {
  baz: [
    number,
    'xiaomuzhu'
  ];
  toString(): string;
  readonly [Symbol.iterator]: 'github';
  0x1: 'foo';
  "bar": 12n;
};

这不是和接口一模一样吗?有点相似,在一定程度上类型字面量可以代替接口

可辨识联合类型

先来假设一个场景,有两个功能,一个是添加用户,一个是删除用户,添加用户不需要id字段,因为会自动生成,而删除用户需要id字段,于是有以下这段代码

ts 复制代码
interface Info {
    username: string;
}
interface UserAction {
    id?: number;
    action: 'create' | 'delete';
    info: Info;
}

先来创建用户

ts 复制代码
const action: UserAction = {
    id: 10,
    action: 'create',
    info: { username: 'juejue' }
}

我们发现当我们创建用户并且传入一个id时是合法的,但是我们明明不需要这个id,如何解决?

ts 复制代码
type UserAction = {
    id: number
    action: 'delete'
    info: Info
} |
{
    action: 'create'
    info: Info
}

const action: UserAction = {
    // id: 10,
    action: 'create',
    info: { username: 'juejue' }
}

在这里使用类型字面量来解决,如果在创建用户时仍然传入id

似乎完美解决问题,但是如果碰上以下这种情况:

ts 复制代码
const UserReducer = (userAction: UserAction) => {
    console.log(userAction.id);
}

我们不清楚是否具有id属性,这里就会报错了

ts 复制代码
const UserReducer = (user: UserAction) => {
    switch (user.action) {
        case 'delete':
            console.log(user.id);
            break;
        default:
            break;
    }
}

可以使用类型守卫来判断

我们上面提到了 user.action 就是辨识的关键,被称为可辨识的标签,我们发现上面这种模式要想实现必须要三个要素:

  • 具有普通的单例类型属性---可辨识的特征,上文中就是 deletecreate 两个有唯一性的字符串字面量
  • 一个类型别名包含联合类型
  • 类型守卫的特性,比如我们必须用 if switch 来判断 user.action 是属于哪个类型作用域即 deletecreate

结尾

知识点有点复杂,得细心看看...

相关推荐
小白学习日记17 分钟前
【复习】HTML常用标签<table>
前端·html
丁总学Java1 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele1 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀1 小时前
CSS——属性值计算
前端·css
xgq1 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
用户3157476081351 小时前
前端之路-了解原型和原型链
前端
永远不打烊1 小时前
librtmp 原生API做直播推流
前端
北极小狐1 小时前
浏览器事件处理机制:从硬件中断到事件驱动
前端
无咎.lsy2 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec2 小时前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron