Interface 接口类型

接口类型的一个作用是将内联类型抽离出来,从而实现类型可复用

TypeScript 对对象的类型检测遵循一种被称之为“鸭子类型”(duck typing)或者“结构化类型(structural subtyping)”的准则,即只要两个对象的结构一致,属性和方法的类型一致,则它们的类型就是一致的。

  1. function Study(language: { name: string; age: () => number }) {
  2. console.log(`ProgramLanguage ${language.name} created ${language.age()} years ago.`);
  3. }
  4. Study({
  5. name: 'TypeScript',
  6. age: () => new Date().getFullYear() - 2012
  7. });
  8. Study({
  9. id: '1', // 类型“{ id: string; name: string; age: () => number; }”的参数不能赋给类型“{ name: string; age: () => number; }”的参数。
  10. // 对象文字可以只指定已知属性,并且“id”不在类型“{ name: string; age: () => number; }”中。ts(2345)
  11. name: 'TypeScript',
  12. age: () => new Date().getFullYear() - 2012
  13. });
  14. const ts = {
  15. id: '1',
  16. name: 'TypeScript',
  17. age: () => new Date().getFullYear() - 2012
  18. };
  19. Study(ts);

有意思的是,对于 TS 的”结构化类型“,如果在实参中定义了形参中没有的属性或者方法,会报错提升。但是如果我们将这个实参赋值给一个变量,这个变量传入给函数调用,将不会报错。那么 TypeScript 静态类型检测就会仅仅检测形参类型中定义过的属性类型,而包容地忽略任何多余的属性,此时也不会抛出一个 ts(2345) 类型错误。

这并非一个疏忽或 bug,而是有意为之地将对象字面量和变量进行区别对待,我们把这种情况称之为对象字面量的 freshness。

除了将实参赋值给一个参数,这种解决方案,还可以使用类型断言和引索签名,万不得已请不要使用 any。

  1. xxx as xx;
  2. interface Config {
  3. width?: number;
  4. [propName: string]: any;
  5. }

可缺省属性

属性可缺省

  1. /** 关键字 接口名称 */
  2. interface OptionalProgramLanguage {
  3. age?: () => number; // (() => number) | undefined;
  4. }

当属性被指定为可缺省属性,它的类型就变成了显示指定类型和 undefined 类型组合的联合类型。

只读属性

属性只读不可修改

  1. interface ReadOnlyProgramLanguage {
  2. /** 语言名称 */
  3. readonly name: string;
  4. /** 使用年限 */
  5. readonly age: (() => number) | undefined;
  6. }
  7. let ReadOnlyTypeScript: ReadOnlyProgramLanguage = {
  8. name: 'TypeScript',
  9. age: undefined
  10. }
  11. /** ts(2540)错误,name 只读 */
  12. ReadOnlyTypeScript.name = 'JavaScript';

定义函数类型

定义函数的类型,而不定义函数的实现

索引签名

索引签名,用来定义对象的映射结构,索引名称的类型分为 string 和 number 两种

  1. interface LanguageRankInterface {
  2. [rank: number]: string;
  3. }
  4. interface LanguageYearInterface {
  5. [name: string]: number;
  6. }
  7. {
  8. let LanguageRankMap: LanguageRankInterface = {
  9. 1: 'TypeScript', // ok
  10. 2: 'JavaScript', // ok
  11. 'WrongINdex': '2012' // ts(2322) 不存在的属性名
  12. };
  13. let LanguageMap: LanguageYearInterface = {
  14. TypeScript: 2012, // ok
  15. JavaScript: 1995, // ok
  16. 1: 1970 // ok
  17. };
  18. }

注意:在上述示例中,数字作为对象索引时,它的类型既可以与数字兼容,也可以与字符串兼容,这与 JavaScript 的行为一致。因此,使用 0 或 ‘0’ 索引对象时,这两者等价。

继承与实现

  1. {
  2. / ** 关键字 接口名称 */
  3. interface ProgramLanguage {
  4. /** 语言名称 */
  5. name: string;
  6. /** 使用年限 */
  7. age: () => number;
  8. }
  9. interface DynamicLanguage extends ProgramLanguage {
  10. rank: number; // 定义新属性
  11. }
  12. interface TypeSafeLanguage extends ProgramLanguage {
  13. typeChecker: string; // 定义新的属性
  14. }
  15. /** 继承多个 */
  16. interface TypeScriptLanguage extends DynamicLanguage, TypeSafeLanguage {
  17. name: 'TypeScript'; // 用原属性类型的兼容的类型(比如子集)重新定义属性
  18. }
  19. }

注意:我们仅能使用兼容的类型覆盖继承的属性

  1. /** 类实现接口 */
  2. {
  3. class LanguageClass implements ProgramLanguage {
  4. name: string = '';
  5. age = () => new Date().getFullYear() - 2012
  6. }
  7. }

Type 类型别名

接口类型的一个作用是将内联类型抽离出来,从而实现类型可复用。其实,我们也可以使用类型别名接收抽离出来的内联类型实现复用。

  1. /** 类型别名 */
  2. {
  3. type LanguageType = {
  4. /** 以下是接口属性 */
  5. /** 语言名称 */
  6. name: string;
  7. /** 使用年限 */
  8. age: () => number;
  9. }
  10. }

Interface 与 Type 的区别

在大多数情况下,Interface 和 Type 是没有区别,它们是等价的,但是在某些特定的场景下这二还有有很大的区别的。比如:重新定义接口的类型,它的类型会叠加,但是 type 会报错。

  1. {
  2. interface languageType {
  3. name: string;
  4. }
  5. interface languageType {
  6. age: () => number
  7. }
  8. const lang: languageType = {
  9. name: '1',
  10. age: () => 1
  11. }
  12. }
  13. {
  14. // 标识符“languageType”重复。ts(2300)
  15. type languageType {
  16. id: number;
  17. }
  18. // 标识符“languageType”重复。ts(2300)
  19. type languageType = {
  20. name: string;
  21. }
  22. const lang: languageType = {
  23. id: 1,
  24. // 不能将类型“{ id: number; name: string; }”分配给类型“languageType”。
  25. // 对象文字可以只指定已知属性,并且“name”不在类型“languageType”中。ts(2322)
  26. name: 'name'
  27. }
  28. }