TypeScript 笔记之对象类型

130 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

1. 对象类型

通过对象类型来描述对象,有三种方式可以描述对象

  1. 通过匿名的形式
function greet(person: { name: string; age: number }) {
  return "Hello " + person.name;
}
  1. 通过接口的形式
interface Person {
  name: string;
  age: number;
}
 
function greet(person: Person) {
  return "Hello " + person.name;
}
  1. 通过类型别名的形式
type Person = {
  name: string;
  age: number;
};
 
function greet(person: Person) {
  return "Hello " + person.name;
}

2. 属性修饰符

2.1 可选属性

可以在属性后面增加一个 ? 表示是可选的

interface PaintOptions {
  xPos?: number;
  yPos?: number;
}

function paintShape(opts: PaintOptions) {}

paintShape({ xPos: 100 });
paintShape({ yPos: 100 });
paintShape({ xPos: 100, yPos: 100 });

这种情况一般会结合解构语法提供属性默认值

function paintShape({ xPos = 0, yPos = 0 }: PaintOptions) {
  console.log("x coordinate at", xPos); // (parameter) xPos: number
  console.log("y coordinate at", yPos); // (parameter) yPos: number
}

2.2 readonly 属性

使用 readonly 属性后,属性本身是不能被修改的。但是,如果属性的值是对象,该属性内部的属性值是可以被修改的

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

function normalHandle(person: Person) {
  console.log(`Happy birthday ${person.info.name}!`);
  person.info.age++;
}

function errorHandle(person: Person) {
  person.info = { //  error TS2540: Cannot assign to 'info' because it is a read-only property.
    name: "leon",
    age: 24,
  };
}

检查两个类型是否兼容的时候不会考虑 readonly 属性,也就是说 readonly 的值可以通过别名进行修改。

interface Person {
  name: string;
  age: number;
}
 
interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}
 
let writablePerson: Person = {
  name: "Person McPersonface",
  age: 42,
};
 
let readonlyPerson: ReadonlyPerson = writablePerson;
 
console.log(readonlyPerson.age); // prints '42'
writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'

3. 属性继承和交叉类型

接口可以使用 extends 关键字从其他接口中继承类型,继承的类型会合并到当前类型。

interface Colorful {
  color: string;
}

interface Circle {
  radius: number;
}

interface ColorfulCircle extends Colorful, Circle {
  id: number;
}

const cc: ColorfulCircle = {
  id: 110,
  color: "red",
  radius: 42,
};

交叉类型使用 & 符号,也可以用于合并已经存在的对象类型

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}

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 });
// error TS2345: 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'?

属性继承和交叉类型最大的区别就是冲突的处理方式,属性继承遇到属性冲突的时候直接报错,而交叉类型不会报错。下面的例子中,通过交叉类型进行处理后,color 的类型是 never,取得是 string 和 number 的交集。

interface Colorful {
  color: string;
}

interface ColorfulSub extends Colorful {
  color: number;
}

// error TS2430: Interface 'ColorfulSub' incorrectly extends interface 'Colorful'.
//   Types of property 'color' are incompatible.
// Type 'number' is not assignable to type 'string'.
interface Colorful {
  color: string;
}

type ColorfulSub = Colorful & {
  color: number
}

4. 泛型对象类型

通过使用泛型,可以生成一个类型模版,可以代替多个具体的类型。

interface Box<Type> {
  contents: Type;
}
interface StringBox {
  contents: string;
}
interface NumberBox {
  contents: number;
}

let boxAA: Box<string> = { contents: "hello" }; // hello
console.log(boxAA.contents);
let boxAB: Box<number> = { contents: 11 }; // 11
console.log(boxAB.contents);

let boxBA: StringBox = { contents: "world" }; // world
console.log(boxBA.contents);
let boxBB: NumberBox = { contents: 22 }; // 22
console.log(boxBB.contents);

5. 小结

首先介绍了描述对象类型的三种方式,匿名、接口和类型别名,接着介绍了可选属性和 readonly 属性,再介绍了属性继承和交叉类型的共同点和区别,最后介绍了泛型对象类型。