0%

对象类型

readonly属性

在 TypeScript 中,属性可以被标记为 readonly,这不会改变任何运行时的行为,但在类型检查的时候,一个标记为 readonly的属性是不能被写入的。

不过使用 readonly 并不意味着一个值就完全是不变的,亦或者说,内部的内容是不能变的。readonly 仅仅表明属性本身是不能被重新写入的。

TypeScript 在检查两个类型是否兼容的时候,并不会考虑两个类型里的属性是否是 readonly,这就意味着,readonly 的值是可以通过别名修改的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Person {
name: string;
age: number;
}

interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}

let writablePerson: Person = {
name: "Person McPersonface",
age: 42,
};

// works
let readonlyPerson: ReadonlyPerson = writablePerson;

console.log(readonlyPerson.age); // prints '42'
writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'

索引签名

索引签名用来描述可能的值的类型

1
2
3
4
5
interface StringArray{
[index:number]:string;
}
const myArray:StringArray=getStringArray();
const secondItem=myArray[1];

我们有了一个具有索引签名的接口StringArray,这个索引签名表示当一个StringArray类型的值使用number类型的值进行索引的时候,会返回一个string类型的值

一个索引签名的属性类型必须是string或者是number

虽然TypeScript同时支持string和number类型,但数字索引的返回类型一定要是字符索引返回类型的子类型,因为当使用一个数字进行索引时,JavaScript实际上把它转成了一个字符串,这意味着使用数字100进行索引跟使用字符串100索引是一样的。

1
2
3
4
5
6
7
8
9
10
11
interface Animal {
name:string
}
interface Dog extends Animal {
breed:string;
}
interface NotOkay {
[x:number]:Animal;
[x:string]:Dog;
//error TS2413: 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
}

属性继承:

有时我们需要一个比其他类型更具体的类型。举个例子,假设我们有一个 BasicAddress 类型用来描述在美国邮寄信件和包裹的所需字段。

1
2
3
4
5
6
7
8
9
10
11
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}

interface AddressWithUnit extends BasicAddress {
unit: string;
}

接口也可以继承多个类型:

1
2
3
4
5
6
7
8
9
10
11
interface Colorful {
color:string;
}
interface Circle {
radius:number;
}
interface ColorfulCircle extends Colorful,Circle{}
const cc:ColorfulCircle={
color:"red",
radius:42,
}

交叉类型

用于合并已经存在的对象类型

1
2
3
4
5
6
7
interface Colorful {
color:string;
}
interface Circle {
radius:number;
}
type ColorfulCircle=Colorful&Circle;

连结Coloeful和Circle产生了一个新的类型,新类型拥有Colorful和Circle的所有成员

1
2
3
4
5
6
7
8
9
10
11
12
function draw(circle:Colorful&Circle){
console.log(`Color was ${circle.color}`);
console.log(`Radius was ${circle.radius}`);
}
// okay
draw({ color: "blue", radius: 42 });

// oops
draw({ color: "red", raidus: 42 });
// Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'.
// Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?

接口继承和交叉类型

区别在于冲突怎么处理,这也是选择要使用哪种方式的主要原因

1
2
3
4
5
6
7
8
9
interface Colorful {
color:string;
}
interface ColorfulSub extends Colorful {
color:number;
}
// Interface 'ColorfulSub' incorrectly extends interface 'Colorful'.
// Types of property 'color' are incompatible.
// Type 'number' is not assignable to type 'string'.

使用继承方式,如果重写类型会导致编译错误,但交叉类型不会

1
2
3
4
5
6
interface Colorful {
color:string;
}
type ColorfulSub=Colorful&{
color:number
}

不报错,color属性的类型是nerver,取得的是string和number的交集

类型别名和接口:

类型别名不同于接口,可以描述的不止是对象类型,我们也可以用类型别名写一些其他种类的泛型帮助类型

1
2
3
4
5
6
7
8
9
10
11
type OrNull<Type> = Type | null;

type OneOrMany<Type> = Type | Type[];

type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;

type OneOrManyOrNull<Type> = OneOrMany<Type> | null

type OneOrManyOrNullStrings = OneOrManyOrNull<string>;

type OneOrManyOrNullStrings = OneOrMany<string> | null

元组:

元组类型是另外一种 Array 类型,当你明确知道数组包含多少个元素,并且每个位置元素的类型都明确知道的时候,就适合使用元组类型

1
type StringNumberPair = [string, number];

元组类型在重度依赖约定的 API 中很有用,因为它会让每个元素的意义都很明显。当我们解构的时候,元组给了我们命名变量的自由度。

元组类型中,可以写一个可选属性,但可选元素必须在最后面,而且会影响类型的length

1
2
3
4
5
6
7
8
type Either2dOr3d=[number,number,number?];
function setCoordinate(coord:Either2dOr3d){
const [x,y,z]=coord;
//const z:number|undefined
console.log(`Provided coordinates had ${coord.length} dismensions`);
//(property) length:2|3
}

Tuples可以使用剩余元素语法,但必须是array/tuple类型:

1
2
3
type StringNumberBooleans=[string,number,...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

有剩余元素的元组不会设置length,因为它只知道在不同位置上的已知元素信息:

1
2
3
4
5
6
7
8
9
const a: StringNumberBooleans = ["hello", 1];
const b: StringNumberBooleans = ["beautiful", 2, true];
const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];

console.log(a.length); // (property) length: number

type StringNumberPair = [string, number];
const d: StringNumberPair = ['1', 1];
console.log(d.length); // (property) length: 2

参数列表使用元组:

1
2
3
function readButtonInput(...args:[string,number,...boolean[]]){
const [name,version,...input]=args;
}

等同于:

1
2
3
function readButtonInput(name:string,version:number,...input:boolean[]){
...
}

readonly元组

大部分代码中,元组只是被创建,使用后不会被修改,所以尽可能将元组设置为readonly是一个好习惯。

1
2
3
4
5
6
7
let point=[3,4] as const;
function distanceFromOrigin([x,y]:[number,number]){
return Math.sqrt(x**2+y**2);
}
distanceFromOrigin(point);
// Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'.
// The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.

尽管distanceFromOrigin并没有更改传入的元素,但函数希望传入一个可变元组,但是point的类型被推断为readonly[3,4],它跟[number,number]并不兼容,所以TypeScript给了一个报错

参考:

https://ts.yayujs.com/handbook/TypeofTypeOperator.html

https://www.typescriptlang.org/docs/handbook/2/typeof-types.html