Interface
在JavaScript中,分组和传递数据的基本方式是通过对象。在TypeScript中,可通过对象类型来表示它们。
它们可以是匿名的:
function greet(person: { name?: string; age: number }) {
return 'Hello ' + person.name;
}
// 'country' does not exist in type '{ name: string; age: number; }'
greet({ name: 'Jason', age: 19, country: 'China' });
或者通过使用接口来命名:interface
interface Person {
name: string;
age: number;
}
function greet(person: Person) {
return 'Hello ' + person.name;
}
再或者通过类型别名:type
type Person = {
name: string;
age: number;
};
function greet(person: Person) {
return 'Hello ' + person.name;
}
interface
一种彼此可接入的规范,符合某一种标准进行定义。通常情况下,类型别名能做的接口都能组做。
interface MyInterface {}
初始化
如果不给对象定义类型,那么对象也不是隐式any,TypeScript会根据对象值进行类型推断。如果需要一个特定属性集合的对象定义的话,则必须显式地进行类型定义。
// obj => { a: number; b: number; }
const obj = {
a: 1,
b: 2
}
// Property 'c' does not exist on type '{ a: number; b: number; }'.
console.log(obj.c);
接口声明
在TypeScript中,interface用来创建一个带有明确结构的对象。它可以定义一系列的属性、方法以及一个类或对象必须实现的事件。接口是对象和类之间的契约,可用于为代码中的对象强制执行特定的结构。
interface Person {
firstName: string;
lastName: string;
getFullName: () => string;
}
定义函数
可通过interface来定义函数:
interface IPlus{
(a: number, b: number): number;
}
const plus: IPlus = (a: number, b: number): number => {
return a + b
}
可选属性
可选属性使得对象的属性变得更加灵活,可兼容不确定数量的属性。在属性名后添加?将属性作为可选的,意味着此属性可有可没有,但如果设置了可选属性,最好明确其类型。
interface PaintOptions {
shape: string;
// optional property
xPos?: number;
yPos?: number;
}
paintShape({ shape: 'square', xPos: 100 });
paintShape({ shape: 'circle', yPos: 100 });
paintShape({ shape: 'triangle', xPos: 100, yPos: 100 });
function paintShape(opts: PaintOptions) {
// 'opts.xPos' is possibly 'undefined'.
let xPos = opts.xPos * 2
}
通过判断或解构给可选属性设置默认值,避免出现错误:
function paintShape(opts: PaintOptions) {
const xPos = opts.xPos === undefined ? 0 : opts.xPos * 2;
const yPos = opts.yPos === undefined ? 0 : opts.yPos * 4;
console.log(xPos + yPos);
}
function paintShape({ shape, xPos = 100, yPos = 100 }: PaintOptions) {
console.log(xPos + yPos);
}
注意:目前无法在在结构模式中放在类型推断,下面的语法在JavaScript意味着不同的东西:
function draw({
shape: string,
xPos: number = 100,
yPos: number = 100,
}: PaintOptions) {
// Cannot find name 'shape'.
console.log(`Drawing a ${shape} at ${xPos}, ${yPos}`);
}
如上解构模式中,shape: string意味着获取shape属性并且重新定义它作为一个局部变量,变量名称为string,因此无法正常使用。
只读属性
在TypeScript中,属性可以被标记为只读的。虽然运行时它不会改变任何的行为,但在类型检测期间只读属性不能被重写。
interface SomeType {
readonly prop: string;
}
function doSomething(obj: SomeType) {
console.log(obj.prop);
// here is error
// Cannot assign to 'prop' because it is a read-only property.
obj.prop = 'hello';
}
readonly修饰符并不代表一个值是完全不可变的,而只是它内部的内容不能改变,仅仅意味着属性自身不能被重写。
interface Home {
readonly resident: { name: string; age: number };
}
function visitForBirthday(home: Home) {
console.log(`Happy birthday ${home.resident.name}!`);
// update property
home.resident.age++;
// but the reference cannot be changed
// Cannot assign to 'resident' because it is a read-only property.
home.resident = {}
}
在检查两种类型是否兼容时,TypeScript不会考虑这两种类型的属性是否只读,因此只读属性可通过别名进行更改。
interface Person {
name: string;
age: number;
}
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
const writablePerson: Person = {
name: 'Jason',
age: 14,
};
const readonlyPerson: ReadonlyPerson = writablePerson;
console.log(readonlyPerson.age); // 14
writablePerson.age++;
console.log(readonlyPerson.age); // 15
上述案例中,只读属性age通过Person类型间接的修改了。
索引签名
有时候并不能提前知道类型属性的所有名称,但的确知道其值的类型。这种情况下,可以使用索引签名描述可能的值类型。
interface StringArray {
[index: number]: string;
}
const myArray: StringArray = ['Bob', 'Fred'];
const firstItem = myArray[0];
如上,接口StringArray有一个索引签名,当一个接口StringArray被一个数字索引时,将返回一个string。
在当前TypeScript中,只有string、number、symbol、模板字符串模式以及这些类型组成的联合类型允许使用索引签名。
虽然字符串索引签名是一种强大的方法去描述字典模式,但是它也强制所有的属性匹配他们的返回类型。由于字符串索引声明obj.property,就相当于obj[’property’]:
interface NumberDict {
[index: string]: number;
length: number;
// here is error
// Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
name: string;
}
某些情况下,可以支持两种类型的索引器,但是数字索引器返回的类型必须是从字符串索引器返回类型的子类型。
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
interface NotOkay {
// [x: number]: Animal;
// [x: string]: Animal;
[x: number]: Dog;
[x: string]: Dog;
// 不兼容
// 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
[x: number]: Animal;
[x: string]: Dog;
}
// const animal: NotOkay = { 0: { name: 'dog' } };
const dog: NotOkay = { 0: { name: 'dog', breed: 'water' } };
如果字符串索引的属性类型是联合类型,那么不同类型的属性是可以接受的:
interface NumberOrStringDict {
[index: string]: number | string;
length: number;
// it's ok !!!
name: string;
}
除此之外,可以让索引签名是只读的,防止给它们重新分配值:
interface ReadonlyStringArray {
readonly [index: number]: string;
}
const myArray: ReadonlyStringArray = ['Alice', 'Bob'];
// here is error
// Index signature in type 'ReadonlyStringArray' only permits reading.
myArray[1] = 'Mallory';
有些情况下,可能希望某个类型通过索引签名定义的属性固定,可采用以下方法:
type TypeKeys = 'a' | 'b' | 'c'
type TObjec = {
[key in TypeKeys]: any
}
const o: TObjec = {
a: 1,
b: '2',
c: 3,
}
过度属性检查
对象被赋值一个类型的位置和方式会影响类型系统。其中一个常见的例子是过度属性检查,在对象创建并赋值给一个对象类型期间更彻底地验证对象。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
return {
color: config.color || 'red',
area: config.width ? config.width * config.width : 20
};
}
/*
Argument of type '{ colour: string; width: number; }' is not assignable to parameter of type 'SquareConfig'.
Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
*/
createSquare({colour: 'red', width: 100});
如上示例,createSquare的参数使用colour代替了color,在纯JavaScript中,这种情况下错误会静默地产生,只有在运行时可能报错。你可能会想,这个参数是兼容的:width和color均为可选属性,而colour属性并不重要。然而,TypeScript认为这可能产生Bug。对象字面量会被特殊对待,当赋值给它们别的变量或者作为函数参数传递,TypeScript会对其进行过度属性检测,如果对象字面量有一些目标类型没有的属性,会产生错误。
绕过这种检测的办法也很简单,其中最简单的一个是使用类型断言:
let res = createSquare({colour: 'red', width: 100} as SquareConfig);
但是有一种更好的解决办法是添加索引签名,当然这是在你确保对象有一些额外属性的情况下。如下:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
此外,还有一种绕开检查的办法:将对象赋值给另一个变量,这样编译器将不会给出错误:
interface SquareConfig {
color?: string;
width?: number;
}
const objOptions = { colour: 'red', width: 100 };
const res = createSquare(objOptions);
以上给出了三种绕过检测的办法,但一般情况下不应该绕过检测,大多数绕过检查都会产生一些Bug。
类型继承
在一个接口中继承另一个接口的成员,以减少重复和声明数量:
interface Animal {
name: string;
age?: string;
}
interface Dog extends Animal {
breed: string;
}
const dog: Dog = {
name: 'dog',
breed: 'dog'
}
此外,一个接口也可以继承多个接口:
interface Animal {
name: string;
age?: string;
}
interface Zoo {
location: string;
}
interface Dog extends Animal, Zoo {
breed: string;
}
const dog: Dog = {
name: 'dog',
breed: 'dog',
location: 'Apple Park'
}
类型断言
类型推断在对象的前提下,不会强制要求必写属性,但是属性的类型必须匹配,且不能增加interface没有的属性。
interface IAccountInfo {
id: number,
name: string,
age: number
}
const info = <IAccountInfo>{
id: 1,
name: "Tom",
// error
age: "12",
// error
address: "No.1"
}