用 interface 会报错改成 type 就好了?从一个报错深入 typescript 中 interface 和 type 的区别

从一个奇怪的报错讲起

在 TypeScript 中, { [props: string]: any } 是一个索引签名的语法,表示一个对象可以有任意数量的属性.比如,我要表达一个 value 是 string 的对象:

vbnet 复制代码
interface ObjectWithStrings { [key: string]: string }

这种写法非常常见,假设我们有一个方法叫 requireObjectStrings,大致抽象一下:

typescript 复制代码
interface ObjectWithStrings { [key: string]: string }
declare const requireObjectStrings: <T extends ObjectWithStrings>(object: T) => void;

这个函数非常简单,用人话描述一下:函数 requireObjectStrings 接受一个对象作为参数,这个对象的属性名和属性值都必须是字符串类型,返回值为 void。

看起来没什么问题,写一个测试用例试试:

php 复制代码
interface MyInterface  {
    foo: string;
}

declare const myInterface: MyInterface;

requireObjectStrings(myInterface)

看起来也没什么问题...等等,ts 编译报错了!

你可以在这里看到 demo

报错信息为:

Argument of type 'MyInterface' is not assignable to parameter of type 'ObjectWithStrings'.

Index signature for type 'string' is missing in type 'MyInterface'.

报错信息有点不太能理解,尝试改一下代码试试,比如把 interface 改成 type...

ini 复制代码
type MyInterface =  {
    foo: string;
}

declare const myInterface: MyInterface;

requireObjectStrings(myInterface)

不报错了🤯

突然有一种一顿瞎改之后代码能正常运行,但是不知道为什么能正常运行的感觉...

尝试解决bug

对于一个公共库来说,我们不能限制用户说你只能用 type 来声明范型。有什么办法解决吗? 上面的 ts 编译报错与索引签名有关,那我试试换个方式来解决:

typescript 复制代码
interface ObjectWithStrings { [key: string]: string }
declare const requireObjectStrings: <T extends {[K in keyof T]: string}>(object: T) => void;

interface MyInterface  {
    foo: string;
}

declare const myInterface: MyInterface;

requireObjectStrings(myInterface)

使用 keyof 关键字,{[K in keyof T]: string} 来约束范型。

Great!编译通过。

bug背后: typescript 中 interface 和 type 的区别

网络上关于 interface 和 type 的区别有很多解释,在这里不赘述了。这个 bug 和 interface 和 type 有什么关系呢?我顺藤摸瓜找到了这个 issue: typescript 的维护者回答道:

Just to fill people in, this behavior is currently by design. Because interfaces can be augmented by additional declarations but type aliases can't, it's "safer" (heavy quotes on that one) to infer an implicit index signature for type aliases than for interfaces. But we'll consider doing it for interfaces as well if that seems to make sense

翻译一下:目前这种行为是有意为之的。因为 interface 可以通过额外声明来增强,但type 不行,所以对 type 推断隐式索引签名比对接口推断要"更安全"(这里的"更安全"要打很多引号)。不过,如果这样做对 interface 也有意义的话,我们也会考虑对 interface 进行类似的推断。

这段话的要点是: 这段话指出了它们之间的一个关键区别:

interface 可以被扩展,意味着你可以在程序的不同部分为同一个接口添加新的属性,TypeScript会将它们合并为一个接口。比如:

css 复制代码
interface Person {
  name: string;
}

interface Person {
  age: number;
}

let p: Person = {
    name: 'li',
    age: 8,
}

以上 Person 类型会被合并。 type 则不可被扩展,一旦定义了 type,它就是固定的,不能再添加新的属性。

因为 type 不可扩展,TypeScript的类型系统在处理类型别名时会更加"宽松",它会对 type 隐式地推断出索引签名。所以 interface 会出现报错,而 type 不会。

上面这段解释可能有点刻意,其实社区的很多讨论认为这个特性算是 typeScript 的 bug。typeScript 的设计在很多地方也是考虑到了实用性和健全性的妥协。如文档所说:

TypeScript's type system allows certain operations that can't be known at compile-time to be safe. When a type system has this property, it is said to not be "sound". The places where TypeScript allows unsound behavior were carefully considered, and throughout this document we'll explain where these happen and the motivating scenarios behind them.

typeScript 的类型系统并不是完全健全的。在类型系统的术语中,"健全"(sound)意味着类型系统能够保证所有的程序都不会在运行时出现类型错误。上面的怪异行为也算是 typeScript 类型系统不健全的一个体现。

总之,如果你要是遇到类似上面的问题,可以选用更安全的 type 而不是 interface 来解决。或者避开索引签名的语法换用别的方式解决。

相关推荐
开开心心就好5 小时前
批量提取PDF中的图片,直接导出原图
前端·javascript·支持向量机·智能手机·pdf·html·启发式算法
Setsuna_F_Seiei8 小时前
前端转型 Agent 开发 05 之 Agent Hooks 与 Checkpointer(让 Agent 从全自动转变人为可掌控)
前端·agent·ai编程
Rain的Java大神之路9 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
百万蹄蹄向前冲9 小时前
风扇转了一晚上MVP专家团翻车事故
前端·人工智能
默_笙9 小时前
🏛 给 AI 配一间办公室:Harness Engineering 六大模块与它的实现
前端·javascript
淡海水10 小时前
13-04-面试-源码级深度追问链
数据结构·unity·面试·c#·游戏引擎·源码·il2cpp
linux_cfan11 小时前
videojs v10 源代码系列解读:14 · 谓词守卫:在运行时安全地调用能力
前端·javascript·音视频
kyriewen11 小时前
我扒了 10,221 条 JD:腾讯技术岗 75% 在要 AI
前端·人工智能·ai编程
郑州光合科技余经理12 小时前
同城外卖小程序开发:下单成功后,后台导出能不能对上用户端状态
开发语言·前端·git·后端·uni-app·php·ai编程
IT_陈寒13 小时前
Vue的响应式让我熬到凌晨三点,原来漏了这个小细节
前端·人工智能·后端