TypeScript对象类型

针对对象的部分,做了如下的回顾

对象属性的三种描述方法:

  • 通过对象本身的key value
  • 通过接口
  • 通过类型别名

属性修饰符

对象中的属性可以指定如下几件事:

  • 是否可选
  • 是否只读
  • 属于什么类型

可选修饰符 ?

通过?修饰符表示属性可选。
当开启strictNullChecks时,会提示潜在的错误

1
2
3
4
5
6
7
8
9
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos;

// (property) PaintOptions.xPos?: number | undefined
let yPos = opts.yPos;

// (property) PaintOptions.yPos?: number | undefined
// ...
}

解决undefined的问题,有两种方法

  • 手动判断是否为undefined
  • 赋默认值(通过解构函数参数赋默认值)

注意,目前没有办法在解构模式中放置类型注释。因为在 JavaScript 中,下面的语法代表的意思完全不同

1
2
3
4
5
6
function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
render(shape);
// Cannot find name 'shape'. Did you mean 'Shape'?
render(xPos);
// Cannot find name 'xPos'.
}

在对象解构语法中,shape: Shape 表示的是把 shape 的值赋值给局部变量 Shape。 xPos: number 也是一样,会基于 xPos 创建一个名为 number 的变量。

只读修饰符 readonly

对于使用readonly的属性,在运行时不会影响任何行为,但在类型检查时不允许写入
readonly仅表示属性本身不能重新写入,并不等于完全不会发生改变(可以对比const声明的对象理解)

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'

索引签名

适用于不知道key值,但知道key值的类型

1
2
3
4
5
6
7
8
interface StringArray {
[index: number]: string;
}

const myArray: StringArray = getStringArray();
const secondItem = myArray[1];

const secondItem: string

这个索引签名表示当一个 StringArray 类型的值使用 number 类型的值进行索引的时候,会返回一个 string类型的值

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

由于js会隐式的将数字索引转为字符串,所以数字索引返回的类型一定是字符串索引返回的子类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Animal {
name: string;
}

interface Dog extends Animal {
breed: string;
}

// Error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
[x: number]: Animal;
// 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
[x: string]: Dog;
}

通过给索引签名设置只读属性,可以控制赋值行为

1
2
3
4
5
6
7
interface ReadonlyStringArray {
readonly [index: number]: string;
}

let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory";
// Index signature in type 'ReadonlyStringArray' only permits reading.

属性继承

通过extends关键字继承接口
interface A extends B

交叉类型

通过&合并两个类型

接口继承与交叉类型

通过接口继承的方式去重写某个属性会报错,但是交叉类型不会报错。

1
2
3
4
5
6
7
interface Colorful {
color: string;
}

type ColorfulSub = Colorful & {
color: number
}

虽然不会报错,那 color 属性的类型是什么呢,答案是 never,取得是 stringnumber 的交集

泛型对象类型

可以理解为一种延迟指定的类型,只有在实例化时才会作为参数指明类型

1
2
3
interface Box<Type> {
contents: Type;
}

可以通过泛型函数来避免函数的重载

1
2
3
function setContents<Type>(box: Box<Type>, newContents: Type) {
box.contents = newContents;
}

数组类型

类型 number[] 或者 string[] 只是 Array<number>Array<string> 的简写形式而已

Array 本身就是一个泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Array<Type> {
/**
* Gets or sets the length of the array.
*/
length: number;

/**
* Removes the last element from an array and returns it.
*/
pop(): Type | undefined;

/**
* Appends new elements to an array, and returns the new length of the array.
*/
push(...items: Type[]): number;

// ...
}

ReadonlyArray 类型

是一个特殊类型,它可以描述数组不能被改变,主要是用来做意图声明。用于让使用者放心传入数组或禁止使用者更改数组

1
2
3
4
5
6
7
8
9
function doStuff(values: ReadonlyArray<string>) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);

// ...but we can't mutate 'values'.
values.push("hello!");
// Property 'push' does not exist on type 'readonly string[]'.
}

ReadonlyArray 并不是一个我们可以用的构造器函数,但可以直接把一个常规数组赋值给 ReadonlyArray
TypeScript 也针对 ReadonlyArray<Type> 提供了更简短的写法 readonly Type[]

1
2
3
4
5
6
7
8
9
function doStuff(values: readonly string[]) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);

// ...but we can't mutate 'values'.
values.push("hello!");
// Property 'push' does not exist on type 'readonly string[]'.
}

Arrays 和 ReadonlyArray 并不能互相赋值

元组类型

是另外一种 Array 类型。适用于元素个数和类型已知。
type StringNumberPair = [string, number];

  • 可以解构元组
    1
    2
    3
    4
    5
    function doSomething(stringHash: [string, number]) {
    const [inputString, hash] = stringHash;
    console.log(inputString); // const inputString: string
    console.log(hash); // const hash: number
    }
  • 可选属性会影响类型的length
  • 可以使用剩余元素语法,但必须是array或tuple(元组)类型
    有剩余元素的元组并不会设置 length
  • 可选元素和剩余元素可以使ts在参数列表里使用元组
    1
    2
    3
    4
    function readButtonInput(...args: [string, number, ...boolean[]]) {
    const [name, version, ...input] = args;
    // ...
    }

readonly 元组类型(readonly Tuple Types)

元组类型也是可以设置 readonly,TypeScript 不会允许写入 readonly 元组的任何属性

1
2
3
function doSomething(pair: readonly [string, number]) {
// ...
}

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

如果我们给一个数组字面量 const 断言,也会被推断为 readonly 元组类型

1
2
3
4
5
6
7
8
9
10
11
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 给了一个报错。

作者

徐云飞

发布于

2022-09-19

更新于

2023-02-05

许可协议

评论