TypeScript 的对象类型

228 阅读3分钟

简介

一旦声明了类型,对象赋值时,就不能缺少指定的属性,也不能有多余的属性。 对象类型可以使用方括号读取属性的类型。

type User = {
  name: string,
  age: number
};
type Name = User['name']; // string

可选属性

读取可选属性之前,必须检查一下是否为undefined。

// 写法一
let firstName = (user.firstName === undefined)
  ? 'Foo' : user.firstName;
let lastName = (user.lastName === undefined)
  ? 'Bar' : user.lastName;

// 写法二
let firstName = user.firstName ?? 'Foo';
let lastName = user.lastName ?? 'Bar';

TypeScript 提供编译设置ExactOptionalPropertyTypes,只要同时打开这个设置和strictNullChecks,可选属性就不能设为undefined。

只读属性

如果属性值是一个对象,readonly修饰符并不禁止修改该对象的属性,只是禁止完全替换掉该对象。

如果一个对象有两个引用,即两个变量对应同一个对象,其中一个变量是可写的,另一个变量是只读的,那么从可写变量修改属性,会影响到只读变量。

interface Person {
  name: string;
  age: number;
}

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

let w:Person = {
  name: 'Vicky',
  age: 42,
};

let r:ReadonlyPerson = w;

w.age += 1;
r.age // 43

如果希望属性值是只读的,除了声明时加上readonly关键字,还有一种方法,就是在赋值时,在对象后面加上只读断言as const。

const myUser = {
  name: "Sabrina",
} as const;

myUser.name = "Cynthia"; // 报错

属性名的索引类型

无法事前知道对象会有多少属性,比如外部 API 返回的对象。这时 TypeScript 允许采用属性名表达式的写法来描述类型,称为“属性名的索引类型”。

type MyObj = {
  [property: string]: string
};

const obj:MyObj = {
  foo: 'a',
  bar: 'b',
  baz: 'c',
};

对象可以同时有多种类型的属性名索引,比如同时有数值索引和字符串索引。但是,数值索引不能与字符串索引发生冲突 可以既声明属性名索引,也声明具体的单个属性名。 属性名的数值索引不宜用来声明数组

解构赋值

目前没法为解构变量指定类型

let { x: foo, y: bar } = obj;

// 等同于
let foo = obj.x;
let bar = obj.y;
let { x: foo, y: bar }: { x: string; y: number } = obj;

结构类型原则

只要对象 B 满足 对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则(structural typing)。

type myObj = {
  x: number;
  y: number;
};

function getSum(obj: myObj) {
  let sum = 0;

  for (const n of Object.keys(obj)) {
    const v = obj[n]; // 报错
    sum += Math.abs(v);
  }

  return sum;
}

// function getSum(obj: MyObj) {
//   return Math.abs(obj.x) + Math.abs(obj.y);
// }

严格字面量检查

如果对象使用字面量表示,会触发 TypeScript 的严格字面量检查(strict object literal checking)。如果字面量的结构跟类型定义的不一样(比如多出了未定义的属性),就会报错。

如果等号右边不是字面量,而是一个变量,根据结构类型原则,是不会报错的。

const myPoint = {
  x: 1,
  y: 1,
  z: 1
};

const point:{
  x:number;
  y:number;
} = myPoint; // 正确

编译器选项suppressExcessPropertyErrors,可以关闭多余属性检查

最小可选属性规则

根据“结构类型”原则,如果一个对象的所有属性都是可选的,那么其他对象跟它都是结构类似的。

如果某个类型的所有属性都是可选的,那么该类型的对象必须至少存在一个可选属性,不能所有可选属性都不存在。这就叫做“最小可选属性规则”。

type Options = {
  a?:number;
  b?:number;
  c?:number;
};

const opts = { d: 123 };

const obj:Options = opts; // 报错

空对象

如果确实需要分步声明,一个比较好的方法是,使用扩展运算符(...)合成一个新对象。

const pt0 = {};
const pt1 = { x: 3 };
const pt2 = { y: 4 };

const pt = {
  ...pt0, ...pt1, ...pt2
};

空对象作为类型,其实是Object类型的简写形式。赋值时总是允许多余的属性,只是不能读取这些属性。 如果想强制使用没有任何属性的对象,可以采用下面的写法。

interface WithoutProperties {
  [key: string]: never;
}

// 报错
const a:WithoutProperties = { prop: 1 };