TypeScript7-交叉与索引

接下来我来讲 TS 的高级类型,所谓高级类型就是指 TS 为了语言的灵活性所引入的一些语言特性,这些特性将帮助我们应对复杂多变的开发场景。本篇文章就来讲 TS 的交叉类型和索引类型。

一、交叉类型

1. 交叉类型的基本使用

交叉类型将多个类型合并为一个类型,新的类型具有所有类型的特性,所以交叉类型特别适合对象混入(mixin)的场景。

interface Person {
    run(): void;
}

interface Teacher {
    goto(): void;
}

let active: Person & Teacher = {
    run() {},
    goto() {}
};

这里我们定义了两个接口 Person 和 Teacher,接口 Person 具有一个方法 run,接口 Teacher 具有一个方法 goto。我们又定义了一个变量 active,它的类型为接口 Person 和 Teacher 的交叉类型,它同时具有方法 run 和 goto。

交叉类型同 "&" 进行连接,此时的变量 active 就应该具备两个接口类型所拥有的成员方法。这里需要注意的是,虽然从名称上看交叉类型给人的感觉是类型的交替,但实际上它是取所有类型的并集。

接下来我们再来看几种和交叉类型相关的类型。

2. 联合类型

联合类型让一个变量可以是多个类型,具有其中一个类型的特性。

let n: string | number = 1;
n = 'abc';

这里我们定义了一个变量 n,它的类型为 string 和 number 的联合类型,可以将 1 和 'abc' 都赋值给它。

联合类型通 "|" 进行连接,此时的变量 n 可以具有 string 和 number 其中一个类型的特性。

3. 字面量类型

有时候我们不仅要限定一个变量的类型,还要限定这个变量的取值在某个特定范围之内,就可以使用字面量类型。

let m: 'm' | 2;
m = 'm';
m = 2;
m = 3; // 报错

这里我们定义了一个变量 m,它的类型是字面量类型的联合类型,表示 m 的取值只能是 'm' 或 2。如果我们给 m 赋值3,就会报错:Type '3' is not assignable to type '"m" | 2'.

4. 对象联合类型

回到上篇文章中的例子,我们给这两个接口分别都重写了 toString 方法。

enum Type { obj, arr }
 
class IsObject {
    toObject() {
        console.log('hello object');
    }
    toString() {
        console.log('hello toString');
    }
}
 
class IsArray {
    toArray() {
        console.log('hello array');
    }
    toString() {
        console.log('hello toString');
    }
}
 
function getType(type: Type) {
    let target = type === Type.obj ? new IsObject() : new IsArray(); // 联合类型
    return target;
}
 
getType(Type.obj);

这里的 target 就是一个 IsObject 和 IsArray 的联合类型。

在这里需要提一下,如果一个变量是一个联合类型,在它的类型没有被确定的情况下,它只能访问所有联合的类型的共用成员。

target.toObject(); // 报错
target.toString();

因此,我们使用 target 变量来调用 toObject 方法会报错,而调用 toString 方法不会报错。

这个时候有趣的事情就发生了,联合类型看似取的是所有类型的并集,但它只能访问所有类型成员的交集。

总结:交叉类型适合做对象的混入,联合类型可以是类型具有不确定性,增加代码的灵活度。

二、索引类型

1. 场景引入

在 JS 中,我们通常会遇到这种场景,比如在一个对象中去获取某些属性的值,再来建立一个集合。我们来实现一下这个过程。

let obj = {
    x: 1,
    y: 2,
    n: 3,
    m: 4
};

function getValue(obj: any, keys: string[]) {
    return keys.map(key => {
        return obj[key];
    });
}

console.log(getValue(obj, ['x', 'y'])); // [1, 2]
console.log(getValue(obj, ['a', 'b'])); // [undefined, undefined]

我们定义了一个对象,里面有一些属性。又定义了一个函数 getValue,用于获取对象 obj 中的某些属性值。我们去获取对象中存在的属性 x 和 y,就会得到正确结果。但如果访问对象中不存在的属性 a 和 b,就会输出 undefined,并不会报错。

如何使用 TS 对这种现象进行约束呢?就可以用到索引类型。要了解索引类型,我们先来了解下索引类型的相关概念。

2. 索引类型相关概念

(1) 索引类型的查询操作符(keyof T)

"keyof T" 表示类型 T 所有公共属性的字面量联合类型。举个简单例子说明下:

interface Person {
    name: string;
    age: number;
}

let person: keyof Person; // 'name' | 'age'

我们定义了一个接口 Person,有两个成员 name 和 age,我们可以使用 keyof 来取出接口 Person 的成员的名字,keyof Person 即为字面量类型 'name' 和 'age' 的联合类型。

(2) 索引访问操作符(T[K])

T[K] 的含义是接口 T 的成员 K 所代表的类型。我们再来看个例子:

interface Person {
    name: string;
    age: number;
}

let personProps: Person['age']; // number

这里我们指定 personProps 的类型为 Person.age 的类型,那么 personProps 的类型就是 number。

3. T extends U

表示泛型变量可以通过继承某个类型获得某些属性。

接下来我们就来改造下 getValue 函数。

首先我们先把 getValue 改造成一个泛型函数,我们需要做一个些约束,这些约束就是 keys 里的元素必须是 obj 的属性。如何做这种约束呢?

我们先来将 getValue 写成泛型函数:

function getValue<T, K>(obj: T, keys: K[]) {
    return keys.map(key => {
        return obj[key];
    });
}

首先我们定义了一个泛型变量 T,来约束 obj。然后又定义了一个泛型变量 K,用来约束 keys 数组。

这里的 K 必须是一个字面量类型的联合类型 "'x' | 'y' | 'n' | 'm'",即为 keyof T。然后让 K 继承 T,即可达成我们想要的效果。

function getValue<T, K extends keyof T>(obj: T, keys: K[]) {
    return keys.map(key => {
        return obj[key];
    });
}

最后我们再来设置函数 getValue 的返回值类型为 T[K][]:

function getValue<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
    return keys.map(key => {
        return obj[key];
    });
}

T 表示我们传进来的 obj 的类型,T[k][] 则表示 getValue 的返回值是一个数组,数组中的成员类型必须要 obj 中属性的类型。

接下来来使用 getValue 函数:

console.log(getValue(obj, ['x', 'y'])); // [1, 2]
console.log(getValue(obj, ['a', 'b'])); // 报错

获取属性 x 和 y 的值可以正确输出。当我们试图去访问对象 obj 中不存在的属性 a 和 b 时,就会报错:Type '"a"' is not assignable to type '"x" | "y" | "n" | "m"'. Type '"b"' is not assignable to type '"x" | "y" | "n" | "m"'.,表示对象 obj 中不存在属性 a 和 b。

索引类型可以实现对对象属性的查询和返回,再配合泛型约束就能让我们使用对象或对象的属性以及属性值之间的一些约束的关系。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴雪月乔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值