typescript 特性之 const 断言

414 阅读2分钟

问题

之前在做网站本地化时, 在类型方面我们直接使用 typeof, 类似这样:

/// type.d.ts
import localeObj from 'file-path/to/locale'
export type Locale = typeof localeObj;

但是这样导出的类型只包含key, 具体的值都变成了string, 类似这样:

{
    foo: string;
    bar: {
        foo_bar: string;
        bar_foo: string;
    }
}

开始也就这么用了, 但是后面在阅读代码的时候, 一旦遇到本地化内容, 编辑器是没有办法提示我, 这个内容具体是什么的, 有些key定义的比较好的还能看出来, 但更多的时候需要跳转到本地化文件才能看到具体内容。 这时我想到的第一个方法就是赋值每个本地化对象, 重新导出为类型:

export const locale = {
    foo: 'foo';
    bar: {
        foo_bar: 'foo_bar';
        bar_foo: 'bar_foo';
    }
}

export type Locale = {
    foo: 'foo';
    bar: {
        foo_bar: 'foo_bar';
        bar_foo: 'bar_foo';
    }
}

但...这不是蛋疼么... 一通尝试后, 我们可以使用一个叫做Const 断言(const assertions)的特性, 来省略掉上面哪个蛋疼类型了:

export const locale = {
    foo: 'foo';
    bar: {
        foo_bar: 'foo_bar';
        bar_foo: 'bar_foo';
    }
} as const

Const 断言

说实话, 这东西要是不知道, 也确实不好搜, 我是之前有点印象, 大概是在TS的哪个版本引入了一个新特性什么的, 当时光看更新日志其实不是很明白具体是做什么的, 这会遇到问题了试一试才终于明白。

这个特性是在TS的3.4版本引入的, 被定义为一个字面量的构造器, 属于类型断言, 具体语法是在值后面断言为const:

// Type '"hello"'
let x = "hello" as const;
// Type 'readonly [10, 20]'
let y = [10, 20] as const;
// Type '{ readonly text: "hello" }'
let z = { text: "hello" } as const;

使用这个断言后, TS就明白:

  • 此表达式中的任何字面类型不应被拓宽
  • 对象字面量会获得readonly属性
  • 数组字面量会变成readonly元组

通过这个类型断言, 就可以省略一些只用于提示编译器不变性的类型(就比如我们上面的例子)

注意事项

const断言只能用于简单的字面量表达式

// Error! A 'const' assertion can only be applied to a
// to a string, number, boolean, array, or object literal.
let a = (Math.random() < 0.5 ? 0 : 1) as const;
let b = (60 * 60 * 1000) as const;
// Works!
let c = Math.random() < 0.5 ? (0 as const) : (1 as const);
let d = 3_600_000 as const;

另外, const断言不能完全的把表达式转为不可变的

let arr = [1, 2, 3, 4];
let foo = {
  name: "foo",
  contents: arr,
} as const;
foo.name = "bar"; // error!
foo.contents = []; // error!
foo.contents.push(5); // ...works!

总结

总的来说, const 断言会让编译器推断一个表达式的类型时尽量收窄, 从而得到更精确的类型定义, 而默认行为则可能会用更宽, 更泛化的类型。 更精确的类型可以让编译器更加了解代码的意图, 也能更准确的区分代码是正确的, 还是有bug的。