TS实用技巧,3分钟搞懂 unknown 和 never!

前端下午茶

共 4448字,需浏览 9分钟

 ·

2021-08-29 21:55


字数: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 的可能类型,目的就是写出类型绝对安全的代码。

三、结束语

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

最后



如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 欢迎加我微信「 sherlocked_93 」拉你进技术群,长期交流学习...

  3. 关注公众号「前端下午茶」,持续为你推送精选好文,也可以加我为好友,随时聊骚。


点个在看支持我吧,转发就更好了



浏览 270
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

分享
举报