TypeScript中的对象

1. 简介

使用大括号表示对象,在大括号内部声明每个属性和方法的类型。

const obj:{
  x:number;
  y:number;
} = { x: 1, y: 1 };

属性的类型可以用分号结尾,也可以用逗号结尾。最后一个属性后面,可以写分号或逗号,也可以不写。

// 属性类型以分号结尾
type MyObj = {
  x:number;
  y:number;
};

// 属性类型以逗号结尾
type MyObj = {
  x:number,
  y:number,
};

一旦声明了类型,对象赋值时,就不能缺少指定的属性,也不能有多余的属性。

读写不存在的属性也会报错,也不能删除类型声明中存在的属性,修改属性值是可以的。

ts不区分对象自身的属性和继承属性,一律视为对象的属性。

读取属性的类型

使用方括号读取属性的类型

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

interface

除了type命令可以为对象类型声明一个别名,ts 还提供了interface命令,可以把对象类型提炼为一个接口。

写法一:type命令

写法二:interface命令

// 写法一
type MyObj = {
  x:number;
  y:number;
};

const obj:MyObj = { x: 1, y: 1 };

// 写法二
interface MyObj {
  x: number;
  y: number;
}

const obj:MyObj = { x: 1, y: 1 };

2. 可选属性

如果某个属性后面加一个问号(?),则这个属性是可选的,可选属性等同于允许赋值为undefined

type User = {
  firstName: string;
  lastName?: string;
};

// 等同于
type User = {
  firstName: string;
  lastName?: string|undefined;
};

读取一个没有赋值的可选属性时,返回undefined

type MyObj = {
  x: string,
  y?: string
};

const obj:MyObj = { x: 'hello' };
obj.y.toLowerCase() // 报错

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

如果将编译选项ExactOptionalPropertyTypesstrictNullChecks同时打开,则可选属性就不能设为undefined。

// 打开 ExactOptionsPropertyTypes 和 strictNullChecks
const obj: {
  x: number;
  y?: number;
} = { x: 1, y: undefined }; // 报错

可选属性与显示设置为undefined的必选属性是不等价的。

type A = { x:number, y?:number };
type B = { x:number, y:number|undefined };

const ObjA:A = { x: 1 }; // 正确
const ObjB:B = { x: 1 }; // 报错

3. 只读属性

如何使用

第一种方法:在属性名前加上readonly关键字,表示这个属性是只读属性,不能修改。只读属性只能在对象初始化期间赋值,此后就不能修改该属性。

interface MyInterface {
  readonly prop: number;
}

第二种方法:就是在赋值时,在对象的后面加上只读断言**as const**

就会变成只读对象了并且不能修改属性。

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

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

注意点:如果变量也声明了类型,则会以变量声明的类型为准。

这里虽然使用了只读断言as const,但是其变量声明的类型不是只读属性,所以可以修改。

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

myUser.name = "Cynthia"; // 正确

属性值为对象

如果属性值是一个对象,readonly修饰符并不禁止修改该对象的属性,只是禁止完全替换掉该对象。【相当于js的引用数据类型】

interface Home {
  readonly resident: {
    name: string;
    age: number
  };
}

const h:Home = {
  resident: {
    name: 'Vicky',
    age: 42
  }
};

h.resident.age = 32; // 正确
h.resident = {
  name: 'Kate',
  age: 23 
} // 报错

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

4. 属性名的索引类型

对象的属性很多,如果一个个声明类型会很麻烦,并且有时候不知道对象有多少个属性,比如外部API,所以ts可以采取属性名表达式的写法来描述类型

属性名的字符串索引

写法:采用表达式写法,写在方括号里面。其中property表示属性名,可以随意命名,它的类型是string,属性值的类型为string。

其中属性的类型有三种为string、number和symbol。

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

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

因为对象可能存在多种类型的属性名索引,但是不能同时存在数值索引和字符串索引,这是因为js内部所有的数值属性名都会自动转为字符串属性名,就会造成一样。

既可以声明属性名索引,也能声明具体的单个属性名,如果单个属性名符合属性名索引的范围,二者就不能冲突,否则会报错。

这里属性名foo符合属性名的字符串索引,但是二者的属性值类型不一样,就会报错。

type MyType = {
  foo: boolean; // 报错
  [x: string]: string;
}

缺点

属性的索引类型写法,建议谨慎使用,因为属性名的声明太宽泛,约束太少。另外,属性名的数值索引不宜用来声明数组,因为采用这种方式声明数组,就不能使用各种数组方法以及length属性,因为类型里面没有定义这些东西。

type MyArr = {
  [n:number]: number;
};

const arr:MyArr = [1, 2, 3];
arr.length // 报错

上面示例中,读取arr.length属性会报错,因为类型MyArr没有这个属性。

5. 解构赋值

解构赋值的类型写法和对象声明类型是一样的,因为解构里的冒号,js指定了其他的用途。

const {id, name, price}:{
  id: string;
  name: string;
  price: number
} = product;

注意:

这里的冒号其实是改为新变量,而不是赋值类型,所以会报错。

function draw({
  shape: Shape,
  xPos: number = 100,
  yPos: number = 100
}) {
  let myShape = shape; // 报错
  let x = xPos; // 报错
}

6. 结构类型原则

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

下面代码中,对象A有一个属性x,类型为number的,对象B满足这个特征,所以说只要使用A的地方就可以使用B。所以B可以赋值给A。

type A = {
  x: number;
};

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

根据“结构类型”原则,ts 检查某个值是否符合指定类型时,并不是检查这个值的类型名(即“名义类型”),而是检查这个值的结构是否符合要求(即“结构类型”)。

ts 之所以这样设计,是为了符合 js 的行为。js 并不关心对象是否严格相似,只要某个对象具有所要求的属性,就可以正确运行。

如果类型 B 可以赋值给类型 A,ts 就认为 B 是 A 的子类型(subtyping),A 是 B 的父类型。子类型满足父类型的所有结构特征,同时还具有自己的特征。凡是可以使用父类型的地方,都可以使用子类型,即子类型兼容父类型。

这样的设计可能会造成报错,比如对象函数的参数处理,如果直接遍历就有可能造成问题,因为只要符合该参数类型的值都能传入。

7. 严格字面量检查

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

定义类型少就会报错。

const point:{
  x:number;
  y:number;
} = {
  x: 1,
  y: 1,
  z: 1 // 报错
};

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

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

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

ts对字面量进行严格检查,是为了防止拼写错误,可以使用一个变量赋值,就不会进行严格检查。

由于严格字面量检查,所以对于字面量对象传入函数时必须很小心,不能有多余的属性。

interface Point {
  x: number;
  y: number;
}

function computeDistance(point: Point) { /*...*/ }

computeDistance({ x: 1, y: 2, z: 3 }); // 报错
computeDistance({x: 1, y: 2}); // 正确

编译器选项suppressExcessPropertyErrors,值为true时,可以关闭多余属性检测。

8. 最小可选属性规则

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

如果一个对象的属性都是可选的,那它就可以是一个空对象,也意味着任意对象都能满足这个结构,为了避免这种情况的发生,ts引入了最小可选属性规则,也称为弱类型检测。

解决方法:

  • 在类型中新增一条索引属性[propName: string]: someType
  • 使用类型断言opt as type

9. 空对象

空对象在ts中是一种特殊值,也是一种特殊类型。

ts的空对象没有自定义属性,只能使用继承的属性,即继承自原型对象Object.prototype的属性。

在ts中对于对象必须一次性声明所有属性。

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

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

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

空对象作为类型,其实是Object类型的简写形式,跟Object类型的行为是一样的。

什么除了null和undefined以外其他类型的值都能赋值给Object,所以它不会有严格的字面量检查。

let d:{};
// 等同于
// let d:Object;

d = {};
d = { x: 1 };
d = 'hello';
d = 2;

如果想强制使用没有任何属性的对象,可以采用下面的写法。

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

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

上面的示例中,[key: string]: never表示属性值的number类型不能赋值给never类型,因此其他对象进行赋值时就会报错。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值