在TS中,我们利用接口(interface)来定义对象的类型。

第一个接口

在前面的章节中,也有用到Interface的地方,在这里我将会再举一个简单的例子

  1. let printObj = (labeledObj: { label: string, size ?: number }) => {
  2. console.log(labeledObj);
  3. };
  4. let myObj = { size: 10, label: "Size 10 Object" };
  5. printObj(myObj);

官网是这么描述的:

The type checker checks the call to printLabel. The printLabel function has a single parameter that requires that the object passed in has a property called label of type string. Notice that our object actually has more properties than this, but the compiler only checks that at least the ones required are present and match the types required.

大体意思是:

类型检查器会查看printLabel的调用。 printLabel有一个参数,并要求这个对象参数有一个名为label类型为string的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。

我们试着再用接口来将上面的例子重写一下:

  1. interface ObjValue {
  2. label: string;
  3. size?: number;
  4. }
  5. function printObj(labeledObj: ObjValue) {
  6. console.log(labeledObj);
  7. }
  8. let myObj = {size: 10, label: "Size 10 Object"};
  9. printObj(myObj);

这里的 ObjValue 就是我们定义的一个接口,它规定了我们接收的对象的数据类型。只要我们传入的对象是满足它的规定的,就允许通过。 值得注意的是,类型检查器并不会去检查属性们的顺序,只需要相应的属性存在,且类型对即可。

可选属性

接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。
下面是应用了“option bags”的例子:

  1. interface ObjValue {
  2. name?: string;
  3. age?: number;
  4. }
  5. interface OutInfo {
  6. name: string;
  7. age: number;
  8. }
  9. function nameAge(date: ObjValue): OutInfo {
  10. const infos: OutInfo = {name: "zxc", age: 24};
  11. if (date.age) {
  12. infos.age = date.age;
  13. }
  14. return infos;
  15. }
  16. let myInfo = nameAge({name: "zxc"});

只读属性

在TS中,某些对象的属性可能仅允许在创建时赋予其值,即该属性是一个只读属性,一般是用 readonly 关键字来标识它。

  1. interface OnlyRead {
  2. readonly age?: number;
  3. readonly name: string;
  4. }
  5. let person: OnlyRead = {
  6. age: 12,
  7. name: "zxc",
  8. };
  9. console.log(person);

如果我们并未指定某属性只读,而在后续经过一系列的数据操作后,又想要将其保护,我们可以这么写:

  1. interface OnlyRead {
  2. age?: number;
  3. name: string;
  4. }
  5. let person: OnlyRead = {
  6. age: 12,
  7. name: "zxc",
  8. };
  9. Reflect.defineProperty(person, "age", {
  10. writable: false, // 不可改
  11. configurable: false, // 不可配置
  12. });

只读数组

如果我们想要让某一数组变成一个只读数组,我们可以这么写:

  1. let arr: ReadonlyArray<number> = [];

如果我们想要将其赋值给一个其它的数组需要这么做:

  1. let arr: number[] = [1, 2, 3];
  2. let arr2: ReadonlyArray<number> = arr; // arr => arr2
  3. let arr3 = arr2 as number[]; // arr2 => arr3

在这里,并不推荐在数组中这么使用。

一般而言,如果是一个数组从拿到开始就需要将其保护的话,我们更推荐使用proxy来处理这样的事情

  1. // 初始接收数组我们拟定就是一个 list:[1,2,3]
  2. // 以vue为示例,我们定义我们要用作使用的数据是 list:[] ,我们还需要定义一个空数据,比如说,我们定义它的名称是: proxy:''
  3. // 我们在拿到数据后可以这样做:
  4. proxy = new Proxy({}, {
  5. get(target, key) {
  6. /** 操作 */
  7. },
  8. set() {
  9. return false;
  10. },
  11. });

额外属性

在某些时候,我们可能会额外地接收一些接口中没有定义过的属性,这些属性又是必要的,但是我们又不能准备地确定这些属性的名称。
官方为我们提供了一些解决方案,但我个人更倾向于推荐以下这种解决方案:

  1. interface ObjValue {
  2. age: number;
  3. name?: string;
  4. [propName: string]: any;
  5. }

函数类型

接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
写一个简单的小例子:

  1. type FuncIter = (name: string, age: number) => boolean;
  2. let person: FuncIter = (name: string, age: number) => {
  3. return true;
  4. };

我们这里用type来表示我们需要定义的函数类型,表示接收的参数第一个需要是string,第二个需要是number,以及函数的返回值应该是一个boolean

如果这边不采用接口,而我们又想要写的更严谨的话,我们需要这样写:

  1. let person: (name: string, age: number) => boolean = (name: string, age: number): boolean => {
  2. return false;
  3. };

可索引类型

与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]ageMap["daniel"]。 可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。

  1. interface StringArray {
  2. [index: number]: string;
  3. }
  4. let myArray: StringArray = ["Bob", "Fred"];
  5. let myStr: string = myArray[0];

Typescript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。

字符串索引签名能够很好的描述dictionary(字典)模式,并且它们也会确保所有属性与其返回值类型相匹配。 因为字符串索引声明了obj.propertyobj["property"]两种形式都可以。 下面的例子里,name的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:

  1. interface NumberDictionary {
  2. [index: string]: number;
  3. length: number; // 可以,length是number类型
  4. name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
  5. }

在这里,我们应该将name这行改为: name: number

另外,我们可以将索引签名设为只读的,不过在平常的业务操作,我们会很少去这样使用:

  1. interface ReadonlyStringArray {
  2. readonly [index: number]: string;
  3. }
  4. let myArray: ReadonlyStringArray = ["Alice", "Bob"];
  5. myArray[2] = "Mallory"; // error!

在一般遇到有这种需求的场景的时候,更远推荐使用proxy来进行完成,当然,这只是个人推荐。