摘要
文章对比了 TypeScript 中方法简写和对象属性两种语法,阐述方法简写语法因双变性可能导致运行时错误,建议使用对象属性语法,还提到可利用 ESLint 规则避免方法简写语法。
1. 两种语法的定义
在 TypeScript 中,对象上定义函数有两种语法方式。一种是方法简写语法,如interface Obj { version1(param: string): void; }
;另一种是对象属性语法,如interface Obj { version2: (param: string) => void; }
。它们看起来很相似,但存在细微差别。
2. 方法简写语法导致运行时错误
2.1 以 Dog 接口为例
首先定义Dog
接口,包含barkAt
方法,interface Dog { barkAt(dog: Dog): void; }
。然后定义SmallDog
接口继承Dog
接口并有额外的whimper
方法。当使用Dog
类型变量时,可以给barkAt
方法的参数标注比Dog
接口预期更窄的类型,如const brian: Dog = { barkAt(dog: SmallDog) {}, };
,这里brian
只想对有whimper
方法的SmallDog
叫。但在barkAt
函数内部可能调用dog.whimper()
,如果传入没有whimper
方法的普通Dog
,如const normalDog: Dog = { barkAt() {}, };
,brian.barkAt(normalDog);
就会产生运行时错误。
2.2 本质原因
这是因为方法简写语法是双变的,即方法可以接受比原始类型更窄或更宽的类型,这种意外的双变性会导致运行时错误。
3. 对象属性语法的优势
如果使用对象属性语法定义方法,如interface Dog { barkAt: (dog: Dog) => void; }
,当试图给方法分配更窄的类型时,TypeScript 会报错。这更符合我们的预期,因为对象属性语法只接受比原始类型更窄的类型。
4. 语法与函数类型无关
一个常见的误解是上述语法与箭头函数和函数声明有关。实际上,两种语法都可以与箭头函数或函数声明一起使用。例如interface Obj { methodShorthand(param: string): void; objectProperty: (param: string) => void; }
,函数声明function functionDeclaration(param: string) {}
和箭头函数const arrowFunction = (param: string) => {}
都可以在两种语法中使用。
5. 与type
的情况相同
使用type
也会出现同样的问题。如type Dog = { barkAt(dog: Dog): void; }
,type SmallDog = { whimper: () => void; } & Dog;
也会出现运行时错误。
6. 如何避免问题
可以使用 ESLint 规则@typescript-eslint/method - signature - style
来避免这个问题,将规则设置为{"rules": {"@typescript-eslint/method-signature-style": ["error", "property"]}}
,这样如果使用方法简写语法就会报错。