TS短文 | 3分钟细品 unknown 和 never 类型

前端达人

共 4278字,需浏览 9分钟

 ·

2021-03-03 02:18

字数:1737字   阅读: 3 分钟

大家好,今天和大家聊下让我曾经迷惑的两个TS类型:unknown 和 never,不知道大家有没有对其用法有所迷惑呢,好记性比不过烂笔头,为了让我不再迷惑,还是通过文字的形式整理下加深下印象比较靠谱,希望今天的分享能给大家解惑。

一、unknown 类型

unknown 类型是 TS3 新增的类型,这个类型与 any 类型类似,可以设置任何的类型值,随后可以更改类型。因此,我们可以将变量先设置为字符串类型,然后再将其设置为数字类型,如果事先不检查类型,使用any类型,调用了不存在的方法,编译时不会报错,代码运行时才会发现错误。但是使用unknown 类型不一样,如果不进行类型判断,执行相关操作编译器就会报错。文字说了这么多,还是 上代码,更容易理解些。

1、关于 Any 的问题

首先我们创建一个 any.ts 的文件,代码如下:

let val: any = 22;
val = "string value";
val = new Array();
val.push(33);
console.log(val);

运行编译后的代码,并不会报错,也是按照我们的预期输出:[33]

由于是 any 类型,我们可以随意更改类型,当变成数组类型时,我们调用push方法进行内容操作,看似没啥问题,如果我们开发人员,由于疏忽,打错了一个不存在的方法,ts代码能正常编译,帮我们发现问题吗?

let val: any = 22;
val = "string value";
val = new Array();
val.doesnotexist(33);
console.log(val);

当运行 tsc any 命令后,你会发现编译器能顺利编译,当我们运行 node any,编译后的代码能正常执行吗?答案是显而易见的,会报异常,你会在控制台发现以下错误:

val.doesnotexist(33);
    ^

TypeError: val.doesnotexist is not a function

上述的错误,大家可能不会犯,但是项目大时,参与的人多时,就很难避免这样类似的问题,因此unknown 类型出现了。

2、一段 unknown 类型的代码

接下来我们来看看它是怎么解决类似的问题,我们还是从一段简单的代码开始,如下段代码所示:

let val: unknown = 22;
val = "string value";
val = new Array();
val.push(33);
console.log(val);

当你编译此代码时,你会立马收到如下报错:

Property 'push' does not exist on type 'unknown'.

是不是很奇怪,虽然我们将其类型更改为数组类型,但是编译器不认识,它认为unknown类型,这个类型没有push方法,当然会报错,除非先判断类型,如果是相关类型且正确执行相关方法,编译器则会顺利通过,如下段代码所示

let val: unknown = 22;
val = "string value";
val = new Array();
if (val instanceof Array) {
    val.push(33);
}
console.log(val);

虽然有些麻烦,但是相比 any 类型说,更加安全,在代码编译期间,就能帮我们发现由于类型造成的问题,因此在大多的场景,建议使用 unknown 类型替代 any。

二、never 类型

这个类型看起来有些奇怪,乍一看,看起来和void相似,但是其完全不一样。从字面意思上来说,表示一个从来不会有返回值的函数(例:while(true) {}),一个总是会抛出错误的函数(function foo() { throw new Error('Not Implemented') })。那么问题来了,它和 void 类型啥区别,void 表示没有任何类型,函数没有返回值时(可以返回,但是没值),我们可以设置为void 类型;never这不一样,一个函数根本就没返回(或者总是出错,永远不会有返回值)。看文字有些费劲,我们还是来看一段简单的代码来理解下吧,如下所示:

function alwaysThrows(): never 
    throw "this will always throw"
    return -1
}

当我们编译上述代码时,编译器就会报错,如下所示:

Type 'number' is not assignable to type 'never'.

编译器已经很明确的告诉了我们 never 类型不应该返回任何值(或抛异常)。那么问题来了,这个类型有啥用呢?我们还是举个例子来理解下吧,比如你有个 enum 枚举类型,代码如下所示:

enum TestNeverEnum { 
    FIRST, 
    SECOND 
}

在 switch 当中判断 type,TS 是可以收窄类型的 (discriminated union):

enum TestNeverEnum {
    FIRST,
    SECOND
}

function getEnumValue(value: TestNeverEnum): string 
    switch (value) { 
        // 这里 value 被收窄为 TestNeverEnum.FIRST
        case TestNeverEnum.FIRST: return "First case"
        // 这里 value 被收窄为 TestNeverEnum.SECOND
        case TestNeverEnum.SECOND: return "Second case"
        // returnValue 是 never 类型
        defaultconst returnValue: never = value; 
    } 
}

注意在 default 里面我们把被收窄为 never 的 returnValue 赋值给一个显式声明为 never 的变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如有一天你的同事增加了TestNeverEnum 枚举类型:

enum TestNeverEnum {
    FIRST,
    SECOND,
    THIRD
}

然而他忘记了在 getEnumValue 里面加上针对 THIRD 的处理逻辑,这个时候在 default branch 里面 returnValue 会被收窄为 TestNeverEnum.THIRD,导致无法赋值给 never(因为有值返回),产生一个编译错误。编译器会产生如下的错误:

Type 'TestNeverEnum' is not assignable to type 'never'.

所以通过这个办法,你可以确保 getEnumValue 方法里总是穷尽 (TestNeverEnum) 了所有 All 的可能类型,目的就是写出类型绝对安全的代码。

三、结束语

今天的内容就到这里,这两个类型你品明白了吗?虽然内容不多,但是需要细品 ,才能理解其应用场景和用好他们,感谢的阅读。


浏览 73
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报