Interface 接口类型
接口类型的一个作用是将内联类型抽离出来,从而实现类型可复用
TypeScript 对对象的类型检测遵循一种被称之为“鸭子类型”(duck typing)或者“结构化类型(structural subtyping)”的准则,即只要两个对象的结构一致,属性和方法的类型一致,则它们的类型就是一致的。
function Study(language: { name: string; age: () => number }) {console.log(`ProgramLanguage ${language.name} created ${language.age()} years ago.`);}Study({name: 'TypeScript',age: () => new Date().getFullYear() - 2012});Study({id: '1', // 类型“{ id: string; name: string; age: () => number; }”的参数不能赋给类型“{ name: string; age: () => number; }”的参数。// 对象文字可以只指定已知属性,并且“id”不在类型“{ name: string; age: () => number; }”中。ts(2345)name: 'TypeScript',age: () => new Date().getFullYear() - 2012});const ts = {id: '1',name: 'TypeScript',age: () => new Date().getFullYear() - 2012};Study(ts);
有意思的是,对于 TS 的”结构化类型“,如果在实参中定义了形参中没有的属性或者方法,会报错提升。但是如果我们将这个实参赋值给一个变量,这个变量传入给函数调用,将不会报错。那么 TypeScript 静态类型检测就会仅仅检测形参类型中定义过的属性类型,而包容地忽略任何多余的属性,此时也不会抛出一个 ts(2345) 类型错误。
这并非一个疏忽或 bug,而是有意为之地将对象字面量和变量进行区别对待,我们把这种情况称之为对象字面量的 freshness。
除了将实参赋值给一个参数,这种解决方案,还可以使用类型断言和引索签名,万不得已请不要使用 any。
xxx as xx;interface Config {width?: number;[propName: string]: any;}
可缺省属性
属性可缺省
/** 关键字 接口名称 */interface OptionalProgramLanguage {age?: () => number; // (() => number) | undefined;}
当属性被指定为可缺省属性,它的类型就变成了显示指定类型和 undefined 类型组合的联合类型。
只读属性
属性只读不可修改
interface ReadOnlyProgramLanguage {/** 语言名称 */readonly name: string;/** 使用年限 */readonly age: (() => number) | undefined;}let ReadOnlyTypeScript: ReadOnlyProgramLanguage = {name: 'TypeScript',age: undefined}/** ts(2540)错误,name 只读 */ReadOnlyTypeScript.name = 'JavaScript';
定义函数类型
定义函数的类型,而不定义函数的实现
索引签名
索引签名,用来定义对象的映射结构,索引名称的类型分为 string 和 number 两种
interface LanguageRankInterface {[rank: number]: string;}interface LanguageYearInterface {[name: string]: number;}{let LanguageRankMap: LanguageRankInterface = {1: 'TypeScript', // ok2: 'JavaScript', // ok'WrongINdex': '2012' // ts(2322) 不存在的属性名};let LanguageMap: LanguageYearInterface = {TypeScript: 2012, // okJavaScript: 1995, // ok1: 1970 // ok};}
注意:在上述示例中,数字作为对象索引时,它的类型既可以与数字兼容,也可以与字符串兼容,这与 JavaScript 的行为一致。因此,使用 0 或 ‘0’ 索引对象时,这两者等价。
继承与实现
{/ ** 关键字 接口名称 */interface ProgramLanguage {/** 语言名称 */name: string;/** 使用年限 */age: () => number;}interface DynamicLanguage extends ProgramLanguage {rank: number; // 定义新属性}interface TypeSafeLanguage extends ProgramLanguage {typeChecker: string; // 定义新的属性}/** 继承多个 */interface TypeScriptLanguage extends DynamicLanguage, TypeSafeLanguage {name: 'TypeScript'; // 用原属性类型的兼容的类型(比如子集)重新定义属性}}
注意:我们仅能使用兼容的类型覆盖继承的属性
/** 类实现接口 */{class LanguageClass implements ProgramLanguage {name: string = '';age = () => new Date().getFullYear() - 2012}}
Type 类型别名
接口类型的一个作用是将内联类型抽离出来,从而实现类型可复用。其实,我们也可以使用类型别名接收抽离出来的内联类型实现复用。
/** 类型别名 */{type LanguageType = {/** 以下是接口属性 *//** 语言名称 */name: string;/** 使用年限 */age: () => number;}}
Interface 与 Type 的区别
在大多数情况下,Interface 和 Type 是没有区别,它们是等价的,但是在某些特定的场景下这二还有有很大的区别的。比如:重新定义接口的类型,它的类型会叠加,但是 type 会报错。
{interface languageType {name: string;}interface languageType {age: () => number}const lang: languageType = {name: '1',age: () => 1}}{// 标识符“languageType”重复。ts(2300)type languageType {id: number;}// 标识符“languageType”重复。ts(2300)type languageType = {name: string;}const lang: languageType = {id: 1,// 不能将类型“{ id: number; name: string; }”分配给类型“languageType”。// 对象文字可以只指定已知属性,并且“name”不在类型“languageType”中。ts(2322)name: 'name'}}
