TS基础篇:TS类型中的any、void和never


在 TS 中,除去直观的一些 number, string, boolean 等类型外,我们也会见到诸如 anyvoidnever 这样的,没有那么直观的类型表达。

any 类型

任意值是 TypeScript 针对编程时类型不明确(或者无需确认类型时)的变量使用的一种数据类型,它常用于以下三种情况。

1、变量的值会动态改变时,比如来自用户的输入,任意值类型可以让这些变量跳过编译阶段的类型检查,示例代码如下:

let x: any = 1;    // 数字类型
x = 'I am who I am';    // 字符串类型
x = false;    // 布尔类型

改写现有代码时,任意值允许在编译时可选择地包含或移除类型检查,示例代码如下:

let x: any = 4;
x.ifItExists();    // 正确,ifItExists方法在运行时可能存在,但这里并不会检查
x.toFixed();    // 正确

定义存储各种类型数据的数组时,示例代码如下:

let arrayList: any[] = [1, false, 'fine'];
arrayList[1] = 100;
function isNumber(value: any) {
    return typeof value === 'number';
}

实际上,TS中对于被标记为 any 类型的变量,是没有进行类型检查而直接通过编译阶段的检查。

We want to opt-out of type checking and let the values pass through compile-time checks. To do so, we label these with the any type.

当然,我个人的观点是,在我们的系统中还是应当尽量避免使用 any 类型,以尽可能的保证系统健壮性。

any 类型的特点

被标记为any类型的变量,具有以下几个特点:

允许赋值为任意类型

let value: any = 'seven';
value = 7;

可以访问任意属性和方法

let value: any = 'hello';

// 可以访问任意属性
console.log(value.name);
console.log(value.name.firstName);

// 可以调用任意方法
value.setName('Jerry');
value.setName('Jerry').sayHello();
value.name.setFirstName('Cat');

要注意的一点是,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

所以当你一旦使用了一个 any 类型后,很可能意味着打开了潘多拉魔盒,会让某一块的代码变得难以维护。而就算使用了断言,也丧失了在静态类型检查阶段发现错误的可能性。

变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型

let value;
value = 'seven';
value = 7;

这里再简单提一个小点,未声明类型的变量虽然一开始被识别为 any 类型,但是经过赋值后,TS 会根据赋值类型来标识变量的类型

什么意思呢,看下面

let value;

value = 'seven';
const diff1 = value - 1;
// 类型检查错误: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type

value = 7;
cosnt diff2 = value - 1;

void 类型

void 类型表示没有任何类型。

void is a little like the opposite of any: the absence of having any type at all

没有返回值的函数,其返回值类型为 void

function warnUser(): void {
    console.log("This is my warning message");
}

申明为 void 类型的变量,只能赋予 undefinednull

let unusable: void = undefined;

never

never 类型表示永远不会有值的一种类型。(很抽象是不是)

The never type represents the type of values that never occur.


举几个例子:

  • never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
// 返回never的函数必须存在无法达到的终点

// 因为总是抛出异常,所以 error 将不会有返回值
function error(message: string): never {
    throw new Error(message);
}

// 因为存在死循环,所以 infiniteLoop 将不会有返回值
function infiniteLoop(): never {
    while (true) {
    }
}
  • 永远不可能存在的情况:
const foo = 123;
if (foo !== 123) {
    const bar = foo;    // bar: never
}

never 类型的一个应用场景

never 类型到底有什么用呢?

举一个我们可能会见到的例子:

// ../../type.ts
enum Letter {
    A,
    B,
    C,
}

// 假如我们有如下一个通用型的业务逻辑
// ../../business.ts
switch (letter) {
    case Letter.A:
        // ...
        break;
    case Letter.B:
        // ...
        break;
    case Letter.C:
        // ...
        break;
    default:
        /**
         * 这里的我们通常会有两种做法:
         *      1. 给一个默认的状态
         *      2. 将其等同于A/B/C的一种
         *
         * 但其实我们默认代码不应该存在进入这一分支的可能,一旦进入意味着程序存在了某种异常的状态。
         **/

        // ...
        break;
}

其实我们默认代码不应该存在进入 default 分支的可能,一旦进入意味着程序存在了某种异常的状态。

假如我们将来在某个时刻给枚举值 Letter 增加了新的类型 D,我们必须手动找到所有 switch 代码并处理,否则将有可能引入 BUG 。

而且这将是一个“隐蔽型”的BUG,如果回归面不够广,很难发现此类BUG。

那 TS 有没有办法帮助我们在类型检查阶段发现这个问题呢?

答案就在下面

switch (letter) {
    case Letter.A:
        // ...
        break;
    case Letter.B:
        // ...
        break;
    case Letter.C:
        // ...
        break;
    default:
        const check: never = customType;

        // ...
        break;
}

由于任何类型都不能赋值给 never 类型的变量,所以当存在进入 default 分支的可能性时,TS的类型检查会及时帮我们发现这个问题。

再补充一点个人观点,在这个 default 分支中,除了使用 never 来帮我们尽可能避免这一问题外,我们依旧需要做好降级处理,因为依然存在可能由于异常进入 default 分支,我们需要采取默认值或者其他合适的方法来保证系统的稳定。

never 和 void 的差异

void 表示没有任何类型,never 表示永远不存在的值的类型。

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

// 这个函数不能申明其返回值类型是 never
function warnUser(): void {
    console.log("This is my warning message");
}

关于本文的内容,参考了很多这一文档,有兴趣的同学可以去看看。也非常感谢作者付出!


文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录