2022还不懂Ts————Ts高级类型

142 阅读5分钟

作者:Sean

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天

前言:TypeScript,简称 ts,等于 type + JavaScript,有了 type 的 JavaScript 就像时刻有绿码在手的你(大误) ts 可以保证在编译时就检查代码中的类型错误和问题,大大减少了空指针异常等问题,而 JavaScript 只能在运行时发现错误 学习如何定义类型、联合类型、交叉类型、类型收紧等。

一、type 和 interface

上一节中,我们学习了如何在声明一个变量时定义它的类型,那如果我只想“定义”一种类型,或者当情况相当复杂时,使用一个“变量”保存下一个复杂的类型可不可以呢?

当然可以,这时候就要使用到typeinterface

type

type PropsA = string;
type PropsB = 'b';

如上面所展示,Props 类型就是一种字符串类型, PropsB 是'b'类型,它定义的变量的值只能为'b'

emmmm....你这有点像那啥放气啊,为啥我不直接使用string'b'呢?

上面说了,也会存在情况比较复杂的场景需要定义一个别名代指,这里还没说到交叉类型和联合类型就先不展开了

这里要特别注意,type 其实并非定义了一种新类型,而是类型别名,即代指,PropsA 并不是一种新类型,它只是指向了字符串类型。

interface

接口?有点奇怪,我们暂且记下他叫声明接口,或者直接叫 interface

interface Props {
  age: number;
}

这咋还有个括号?里面这有点像键值对啊?

没错,interface 只能声明对象类型,并且它可以像 class 一样去继承其他 interface 的类型,是一种面向对象的思想。

interface PropsB extends Props {
  name: string;
}
const obj: PropsB = {
  name: 'sean',
  age: 18,
};

区别

  1. interface 只描述对象,type 则描述所有数据
  2. interface 是类型声明(实际存在的类型),type 只是类型别名(实际不存在)
  3. type 不能重新赋值,方便快速计算,不易扩展。interface 方便扩展
  4. 遇到冲突时,interface 直接报错,type 会把类型赋成 never

前面两点很好理解,三四点是怎么回事呢? 直接上代码

type.jpg

type2.jpg

type A = string;
type A = number; // 报错了
type C = string & number; // never
interface Person {
  name: string;
}
interface Person {
  name: number; // 报错了
}
interface Person {
  age: number;
}
// 没有报错
const a: Person = {
  name: 'sean',
  age: 1,
};

interface.jpg

2ce9186b9dce0b4ad2b428d58440844.jpg 那这有什么用呢?当我们开发了对外开放的库或者包,尽量使用 interface,别人在使用的时候,就可以扩展我们定义的属性了。我们可以在项目里新建一个以.d.ts 结尾的文件,引入要扩展的类型,进行扩展

import X from './xx';

declare module 'xxx' {
  // 导出新的X类型
  export interface X {
    yyy: number;
  }
}

二、联合类型 |

type A = { name: string };
type B = { age: number };
type C = A | B;
const c: C = { name: 'sean' };
const c2: C = { age: 18 };
const c3: C = { name: 'sean', age: 81 };

从上面可以看出,C 即可以是 A 也可以是 B,如果是对象类型,也可以是 A 和 B 中类型的联合。所以当我们不确定一个变量是字符串类型还是数字类型时,我们可以使用联合类型定义type Props = string | number

类型收窄

这样很方便,但是在使用的时候,需要进行类型收窄。因为 Props 类型既可以是字符串,也可以是数字,那当它是字符串时,就不能使用数字类型的方法,例如toFixed()。如何进行类型收窄呢?方法有很多

  • typeof,instanceof,in(区分不出可选类型)
  • 直接赋值,逻辑判断……
  • 每一个都加上 kind 索引(只能是基本类型,且没有交集)
  • 断言 as
type Props = string | number;
let p: Props;
p.toFixed(2); // 报错
if (typeof p === 'number') p.toFixed(2); // 成功

疑问

到这里,就有两个疑问了

  1. any 是所有类型的联合类型吗?
  2. 什么类型时所有类型的联合类型?

(你这问题问的好没水平啊)

  1. 显然不是,因为联合类型需要进行类型收窄,才能使用对应类型的方法和属性,而 any 可以使用任意属性和方法
  2. unknown,unknown 只能使用所有属性共有的属性和方法,如果想用特有的类型属性或方法,需要先收窄类型。所以我们提倡使用 unknown,这样更安全。

三、交叉类型 &

联合类型是可以是也可以是,而交叉类型就是即是也是。

哦。。。。那一个类型可不可以即是字符串也是数字呢?

type Props = string & number;
let p: Props; // never
type A = { name: string };
type B = { age: number };
type C = A & B; // { name: string; age: number }

显然,一个类型不可以即是字符串也是数字,当两个基础类型交叉,只能得到never,所以交叉类型主要是个对象类型使用的。如果两个对象类型中有相同的键,且都为基础类型,交叉后也为never,不存在覆盖关系

type C = { name: string } & { name: number }; // { name: never }

结语

这节简单的介绍了类型别名、类型声明、联合类型和交叉类型,当然还有更复杂的例如函数的联合、交叉将会在函数篇介绍。