Exploring TypeScript Type Annotations - Defining Types
作者: zhilidali
欢迎来到 《探索 TypeScript 类型注解》 系列教程。 上一篇介绍了 TypeScript 的原生数据类型,本篇探索如何创建自定义类型 (约定类型首字母大写),来描述程序中复杂的数据类型。
目录
1 接口
接口通过 interface 关键字定义,定义的类型可以用来描述对象、数组、函数、类等。
// 定义声明一个 MyType 类型
interface MyType {}
对象结构
基本语法: K: T 描述对象的形状(结构)
// 定义属性 a 为字符串类型,属性 b 为数值类型
interface MyType {
a: string;
b: number;
}
// 使用自定义的 MyType 类型
let bar: MyType = { a: 'TS', b: 1 }; // Ok
let baz: MyType = { a: 1000, b: 1 }; // Error
let foo: MyType = { a: 'TS' }; // Error (结构不一致)
可选属性: K?: T
interface MyType {
a: string;
b?: number
}
let foo: MyType = { a: 'TS' }; // Ok
只读属性: readonly K: T
interface MyType {
a: string;
readonly b: number;
}
let bar: MyType = { a: 'TS', b: 1 };
bar.a = 'TypeScript'; // Ok
bar.b = 2; // Error
索引签名
- 字符串索引签名 String Index Signature
- 数值索引签名 Number Index Signature
字符串索引签名:[K: string]: T 描述任意属性
interface MyType {
a: string;
[k: string]: string;
}
let baz: MyType = { a: 'TS', b: 'foo' };
数值索引签名:[K: number]: T 描述数组类型
interface MyType {
[index: number]: string
}
let arr1: MyType = ['a', 'b', 'c']; // Ok
let arr2: MyType = ['a', 'b', 100]; // Error
调用签名
调用签名 Call Signature: (parameter: T): U 描述函数 (参数和返回值) 类型
interface FnType {
(a: number): boolean; // 参数 a 为 number 类型,返回 boolean 类型
}
let fn: FnType = (n: number) => !n;
接口与类
类具有实例与静态两部分构成。
- implements 实现接口:
class Foo implements T {}描述类的实例部分 - Construct Signature 构造器签名 =>
new (): T描述类的构造函数
// 可通过 implements 描述类的实例部分
interface ClassType {
foo: string;
setFoo(str: string): void;
}
// 构造器签名用来描述构造函数
interface ConstructorType {
new (n: number): any;
}
const Foo: ConstructorType = class Foo implements ClassType {
foo: string = 'foo';
setFoo(str: string) {
this.foo = str;
}
constructor(n: number) { }
}
类作为接口
在 TS 中,类不仅会声明类类变量,还声明了一个类型,所以类可以作为接口使用
class A {
x: number = 1;
foo(): number { return 1 }
}
const a: A = new A(); // `: A` 作为接口使用
a.x;
a.foo();
接口继承 Interface Extending
接口也可以如同 JS 的类一样,可以使用 extends 实现继承。
- Interface Extending Interfaces 接口继承接口
- Interface Extending Classes 接口继承类 (类可以作为接口)
interface Type1 { foo: string }
// 接口 Type2 继承接口 Type1
interface Type2 extends Type1 { bar: string }
const obj1: Type2 = {
foo: 'foo',
bar: 'bar',
};
class Baz {
baz: string;
constructor(str: string) {
this.baz = str;
}
}
// 接口 Type3 继承类 Baz
interface Type3 extends Baz { qux: string }
const obj2: Type3 = {
baz: 'baz',
qux: 'qux',
};
混合类型
混合类型 Hybrid Type: 比如 JS 中的函数也可以作为对象,为其添加属性
interface MyType {
(n: number): string;
a: number;
}
// 函数 foo 本质是对象,可添加属性 a
function foo(n: number): string { return '' }
foo.a = 123;
let bar: MyType = foo;
2 类型别名
Type Alias 类型别名使用 type 关键字,创建一个引用其他类型的新名字。
type Str = string;
let s: Str = 'foo';
type Obj = {
a: number;
}
let obj: Obj = { a: 1 };
类型别名 VS. 接口
type 创建的名字引用其它类型。
- 类型别名可以命名原始类型、元组等
interface 创建了一个新的类型。
- 接口可以继承
- 接口可以声明合并 (后续篇幅会介绍)
3 泛型
Generic 泛型好比 JS 中的函数,使用尖括号包裹类型参数:
<T>。
泛型变量
泛型变量 Generic Type Variable: <T, U...>
- Generic Function 泛型函数
- Generic Interface 泛型接口
- Generic Class 泛型类
泛型函数 : <T>(): ReturnType
// 通过 <T> 传递类型参数,下面示例约束参数和返回值类型一致
function identity<T>(arg: T): T {
return arg;
}
// 泛型好比 JS 中的函数,使用时需传入类型
let foo = identity<string>('TS');
// 由于TS 会自动推断类型,可省略类型传参
let bar = identity('TS');
泛型接口 :interface TypeName<T> {}
// 使用 interface 描述上面函数
interface GenericInterface1 {
<T>(arg: T): T;
}
// 可将泛型参数当作整个接口的参数
interface GenericInterface2<T> {
(arg: T): T;
}
let baz: GenericInterface2<string> = identity;
泛型类 :class className<T> {}
// 泛型类与泛型接口差不多,`<>`跟在类名后面
class GenericClass<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
return x + y;
};
泛型约束
假设一个函数接受一个参数,返回参数的属性 foo。
function getFoo<T> (arg: T) {
return arg.foo; // Error: Property 'foo' does not exist on type 'T'
}
使用泛型变量定义,由于事先不知道参数的类型,会显示错误,我们可以使用泛型约束 <T extends U>。
interface FooProp {
foo: number;
}
// 泛型 `T` 必须符合接口 `FooProp`,即必须包含 `foo` 属性
function getFoo<T extends FooProp> (arg: T) {
return arg.foo;
}
getFoo({ foo: 3 }); // Ok, getFoo<{ foo: number}>({ foo: 3})
getFoo({ foo: 'foo' }); // Error
结语
本篇介绍了如何自定义类型,下篇介绍类型的检查规则。
协议
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
《探索 TypeScript 类型注解》
参考链接
Handbook: github.com/microsoft/T…