TypeScript 对象类型列出对象拥有的属性以及每个属性的类型:{ name: string; age: number }。加上 ? 让属性变为可选,加上 readonly 防止属性被重新赋值。可以内联写出类型,也可以用 type 或 interface 给它命名以便复用。
编写对象类型
属性之间用 ; 或 , 分隔(两者都可以,; 是常见风格),单独换行也可以。只用一次的参数用内联类型没有问题;用到两次以上的,就给它起个名字。
// Inline, in a parameter
function area(rect: { width: number; height: number }): number {
return rect.width * rect.height;
}
// Named with a type alias
type Rect = { width: number; height: number };
// Named with an interface (the same shape)
interface RectShape {
width: number;
height: number;
}
type 和 interface 描述对象形状的能力一样好。它们的区别(声明合并、联合类型)见 interface 与 type 页面。
访问类型中没有声明的属性会导致编译错误:point.z 报 TS2339:Property 'z' does not exist on type '{ x: number; y: number; }'.
可选属性
名字后面的 ? 让这个属性可以省略。读取可选属性得到 T | undefined,所以 TypeScript 会要求你在使用之前处理缺失的情况。
不加检查就调用可选属性上的方法会报错:p.nickname.toUpperCase() 报 TS18048:'p.nickname' is possibly 'undefined'.。如果可以接受 undefined 作为结果,就使用可选链(p.nickname?.toUpperCase())。
prop?: T 和 prop: T | undefined 并不相同。前者允许这个键不存在;后者要求这个键存在,只是它的值可以是 undefined:
只读属性
readonly 禁止在对象创建之后重新给属性赋值。它只是编译时的检查,而且是浅层的:存放在只读属性中的对象或数组,其内部仍然可以修改。
输出展示了这两个限制:id 在运行时确实被改掉了(只有编译器知道它是只读的),内部的数组也被修改了。只读数组用 readonly string[];要一次性让所有属性只读,用 Readonly<Order>。
多余属性检查
当你把对象字面量直接赋给带类型的变量,或者直接传给带类型的参数时,TypeScript 会拒绝类型中没有声明的任何属性。新创建的字面量里出现额外属性,几乎都是拼写错误。
index.ts(8,8): error TS2561: Object literal may only specify known properties, but 'colour' does not exist in type 'Options'. Did you mean to write 'color'?
这里是 TS2561,因为编译器找到了相近的属性名;如果多出的属性没有相似的名字,就会报 TS2353:Object literal may only specify known properties, and 'z' does not exist in type 'Point'.。如果没有这项检查,拼写错误就能编译通过,color 会是 undefined,程序会悄无声息地用黑色绘制。这项检查只针对新创建的字面量。已经存放在变量中的对象可以带有额外属性,因为 TypeScript 的类型是结构化的:只要一个值至少具备所需的属性,它就符合这个类型。
嵌套对象与方法
对象类型可以嵌套,也可以用方法语法或函数类型的属性来描述方法。
对于层级深或需要复用的形状,给内层类型命名(type Address = { ... })并引用它,或者像最后几行那样,用索引访问 Company["address"] 从外层类型中取出它。
object、{} 与 Object
有三个类型听起来很像,含义却各不相同:
| 类型 | 接受 | 拒绝 |
|---|---|---|
object | 任何非基本类型:{}、[]、函数、类的实例 | 5、"a"、true、null、undefined |
{} | 除 null 和 undefined 之外的任何值,包括基本类型 | null、undefined |
Object | 和 {} 相同,另外还检查 toString 这类内置成员是否保持兼容的类型 | null、undefined |
{ x: number } | 任何带有数字类型 x 的值 | 没有 x 的值 |
{} 的意思不是“空对象”,而是“不是 null 或 undefined”。要接受键未知的任意对象,使用 Record<string, unknown>;要表示键到值的映射,使用索引签名或 Record,见 字典 页面。大多数时候,具体的形状比这三个类型都要好。
常见问题
TypeScript 中如何定义对象类型?
在花括号中列出属性及其类型:{ name: string; age: number }。你可以把它内联写在类型注解中,也可以用 type User = { ... } 或 interface User { ... } 给它命名并重复使用。属性之间用 ; 或 , 分隔。
TypeScript 中如何让属性变为可选?
在属性名后面加 ?:{ name: string; nickname?: string }。对象可以省略 nickname,读取它得到 string | undefined,所以在把它当作字符串使用之前,你必须检查它或提供默认值(user.nickname ?? user.name)。
prop?: string 和 prop: string | undefined 有什么区别?
使用 prop?: string 时,这个键可以完全省略。使用 prop: string | undefined 时,这个键是必需的,只是它的值可以是 undefined,所以 {} 会编译报错。读取两者得到的都是 string | undefined。
TypeScript 中 object、{} 和 Object 有什么区别?
object 表示任何非基本类型的值(对象、数组、函数),会拒绝 5 或 "a"。{} 表示除 null 和 undefined 之外的任何值,包括基本类型。Object 和 {} 几乎一样,但还会检查 toString 这类内置方法是否保持兼容的类型。应使用 object,或者更好的做法是使用具体的形状,而不是 {} 或 Object。
为什么 TypeScript 提示对象字面量只能指定已知属性?
这是多余属性检查:错误 TS2353,或者当编译器能推测出你想写的属性时,报 TS2561。当你把一个新创建的对象字面量直接赋给带类型的变量或参数时,类型中没有声明的任何属性都会被标记出来,因为这通常是拼写错误。如果赋值的是存放在另一个变量中的对象,就会跳过这项检查,因为结构类型允许额外的属性。