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', // ok
2: 'JavaScript', // ok
'WrongINdex': '2012' // ts(2322) 不存在的属性名
};
let LanguageMap: LanguageYearInterface = {
TypeScript: 2012, // ok
JavaScript: 1995, // ok
1: 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'
}
}