Arrayconst 断言和类型推断在本章中,我们将研究如何在 TypeScript 中对数组进行类型定义。
数组在 JavaScript 中可以扮演以下角色(单个角色或多个角色混合):
TypeScript 通过提供多种数组类型定义方式来适应这两种角色。接下来我们将逐一介绍。
Array数组类型字面量由元素类型后跟 [] 组成。在下面的代码中,数组类型字面量是 string[]
// Each Array element has the type `string`:
const myStringArray: string[] = ['fee', 'fi', 'fo', 'fum'];数组类型字面量是使用全局泛型接口类型 Array 的简写形式
const myStringArray: Array<string> = ['fee', 'fi', 'fo', 'fum'];如果元素类型更复杂,我们需要为数组类型字面量添加括号
(number|string)[]
(() => boolean)[]在这种情况下,泛型类型 Array 更适用
Array<number|string>
Array<() => boolean>如果数组具有固定长度,并且每个元素都有不同的、固定的类型,具体取决于其位置,则可以使用元组类型字面量,例如 [string, string, boolean]
const yes: [string, string, boolean] = ['oui', 'sí', true];如果一个接口只有一个索引签名,我们可以将其用于数组
interface StringArray {
[index: number]: string;
}
const strArr: StringArray = ['Huey', 'Dewey', 'Louie'];同时具有索引签名和属性签名的接口只能用于对象(因为索引元素和属性需要同时定义)
interface FirstNamesAndLastName {
[index: number]: string;
lastName: string;
}
const ducks: FirstNamesAndLastName = {
0: 'Huey',
1: 'Dewey',
2: 'Louie',
lastName: 'Duck',
};由于数组的两种角色,TypeScript 不可能总是猜到正确的类型。例如,考虑以下分配给变量 fields 的数组字面量
const fields: Fields = [
['first', 'string', true],
['last', 'string', true],
['age', 'number', false],
];fields 的最佳类型是什么?以下都是合理的选择
type Fields = Array<[string, string, boolean]>;type Fields = Array<[string, ('string'|'number'), boolean]>;type Fields = Array<Array<string|boolean>>;type Fields = [
[string, string, boolean],
[string, string, boolean],
[string, string, boolean],
];type Fields = [
[string, 'string', boolean],
[string, 'string', boolean],
[string, 'number', boolean],
];type Fields = [
Array<string|boolean>,
Array<string|boolean>,
Array<string|boolean>,
];当我们使用非空数组字面量时,TypeScript 默认推断列表类型(而不是元组类型)
// %inferred-type: (string | number)[]
const arr = [123, 'abc'];唉,这并不总是我们想要的
function func(p: [number, number]) {
return p;
}
// %inferred-type: number[]
const pair1 = [1, 2];
// @ts-expect-error: Argument of type 'number[]' is not assignable to
// parameter of type '[number, number]'. [...]
func(pair1);我们可以通过向 const 声明添加类型注释来解决这个问题,这可以避免类型推断
const pair2: [number, number] = [1, 2];
func(pair2); // OK如果我们使用空数组字面量初始化一个变量,则 TypeScript 最初会推断出类型 any[],并随着我们进行更改而逐步更新该类型
// %inferred-type: any[]
const arr1 = [];
arr1.push(123);
// %inferred-type: number[]
arr1;
arr1.push('abc');
// %inferred-type: (string | number)[]
arr1;请注意,最初推断的类型不受以后发生的事情的影响。
如果我们使用赋值而不是 .push(),则工作原理相同
// %inferred-type: any[]
const arr1 = [];
arr1[0] = 123;
// %inferred-type: number[]
arr1;
arr1[1] = 'abc';
// %inferred-type: (string | number)[]
arr1;相反,如果数组字面量至少有一个元素,则元素类型是固定的,以后不会更改
// %inferred-type: number[]
const arr = [123];
// @ts-expect-error: Argument of type '"abc"' is not assignable to
// parameter of type 'number'. (2345)
arr.push('abc');const 断言和类型推断我们可以在数组字面量后面加上 const 断言
// %inferred-type: readonly ["igneous", "metamorphic", "sedimentary"]
const rockCategories =
['igneous', 'metamorphic', 'sedimentary'] as const;我们声明 rockCategories 不会改变。这会产生以下影响
数组变为 readonly - 我们不能使用更改它的操作
// @ts-expect-error: Property 'push' does not exist on type
// 'readonly ["igneous", "metamorphic", "sedimentary"]'. (2339)
rockCategories.push('sand');TypeScript 推断出一个元组。比较
// %inferred-type: string[]
const rockCategories2 = ['igneous', 'metamorphic', 'sedimentary'];TypeScript 推断字面量类型("igneous" 等),而不是更通用的类型。也就是说,推断的元组类型不是 [string, string, string]。
以下是更多带有和不带有 const 断言的数组字面量示例
// %inferred-type: readonly [1, 2, 3, 4]
const numbers1 = [1, 2, 3, 4] as const;
// %inferred-type: number[]
const numbers2 = [1, 2, 3, 4];
// %inferred-type: readonly [true, "abc"]
const booleanAndString1 = [true, 'abc'] as const;
// %inferred-type: (string | boolean)[]
const booleanAndString2 = [true, 'abc'];const 断言的潜在陷阱const 断言有两个潜在的陷阱。
首先,推断的类型尽可能窄。这会导致 let 声明的变量出现问题:我们不能分配除用于初始化的元组之外的任何其他元组
let arr = [1, 2] as const;
arr = [1, 2]; // OK
// @ts-expect-error: Type '3' is not assignable to type '2'. (2322)
arr = [1, 3];其次,无法修改通过 as const 声明的元组
let arr = [1, 2] as const;
// @ts-expect-error: Cannot assign to '1' because it is a read-only
// property. (2540)
arr[1] = 3;这既不是优点也不是缺点,但我们需要意识到这一点。
每当我们通过索引访问数组元素时,TypeScript 总是假设索引在范围内(A 行)
const messages: string[] = ['Hello'];
// %inferred-type: string
const message = messages[3]; // (A)由于这个假设,message 的类型是 string。而不是我们可能预期的 undefined 或 undefined|string。
如果我们使用元组类型,我们会收到错误消息
const messages: [string] = ['Hello'];
// @ts-expect-error: Tuple type '[string]' of length '1' has no element
// at index '1'. (2493)
const message = messages[1];as const 也会产生相同的效果,因为它会导致推断出元组类型。