TypeScript (五)

86 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

😊 大家好,我是思淼MJ。

上篇文章学习了 TypeScript 的联合类型,今天我们继续学习 TyptScript 的另外一种类型。

对象类型 - 接口

什么是对象类型 - 接口???

说到“接口”,我们可以会立马想到,在开发项目中,后端同学提供给前端同学的功能接口(不知道有没有和我一样,我第一个想到的就是这个😂)。

在面向对象语言中,接口是一个很重要的概念,它是指,对行为的抽象,而具体如何行动需要由类去实现。

在 TyptScript 中,接口是一个非常灵活的概念,它除了可用于对类的一部分行为进行抽象之外,也常常用于对 对象形状 进行描述,所以为称为“对象类型 - 接口”。

举个简单的例子🌰:

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

在接口的定义时,我们使用 interface 定义一个接口类型,接口名一般首字母大写。上面的例子中,定义了一个接口类型 person,使用此接口类型:

let Peter:Person = {
    name: 'Peter';
    age: 12;
}

这样就约束了 Peter 的形状必须和接口 Person 保持一致。

🤔问题:当定义的变量比接口少一些属性时,是否可以?

📖答案:不允许。

对上面的例子调整🌰:

interface Person {
    name: string;
    age: number;
};
let Peter:Person = {
    name: 'Peter';
}

执行上面的例子,你会发现报错:

//   Type '{ name: string; }' is not assignable to type 'Person'.
//   Property 'age' is missing in type '{ name: string; }'.

🤔问题:那当定义的变量比接口多一些属性时,是否可以?

📖答案:仍旧是不允许的。

如对上面例子进行调整🌰:

interface Person {
    name: string;
    age: number;
};
let Peter:Person = {
    name: 'Peter',
    age: 18,
    from: 'Beijing'
}

执行这块代码,你会发现报错:

//   error TS2322: Type '{ name: string; age: number;from: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'from' does not exist in type 'Person'.

总结:

当我们定义一个接口类型的,并使用此接口类型定义变量的时候,这个变量的形状(形状:可以理解为属性)的个数,以及个数的类型都是固定的,不可多也不可少,否则就会报错。

可选属性

看到“可选”也许你就猜到了是什么意思,“可选”:可选择的,也就是说不是必须要存在的属性。

项目中我想或多或少都会遇到这种的情况:在和后端接口进行交互的时候,某个属性有的话就传递参数,如果没有就不传,这个时候就需要用到“可选属性”。

举个简单的例子🌰:

interface Person {
    name: string;
    age?: number;
}
let Lili:Person = {
    name: 'Lili'
}
let Mingming:Person = {
    name: 'Mingming',
    age: 20
}

可选属性,使用 ?:表示

🤔问题:如果添加未定义的属性,是被允许的吗?

📖答案:不允许。

比如对上面的例子调整🌰:

interface Person {
    name: string;
    age?: number;
}
let Lili:Person = {
    name: 'Lili'from: 'Shanghai'
}

执行这块代码,你会同样发现,报错了:

//   error TS2322: Type '{ name: string; age: number;from: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'from' does not exist in type 'Person'.

总结:

可选属性的含义是,接口类型的可选属性是可以不存在的;仍然不允许添加未定义的属性。

任意属性

任意类型就是字面意思,即 任意的属性(any)。

举个简单的例子🌰:

interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}
let Lili:Person = {
    name: 'Lili',
    from: 'Shanghai'
}

在 TypeScript 中,使用 [propName: string] 来定义任意属性取 string 类型的值。

这里需要注意的点是,当定义了任意类型,那么确定的属性和可选属性都必要它的类型的子集;一个接口类型只能定义一个任意类型。

比如:

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}
let Lili:Person = {
    name: 'Lili',
    age: 18,
    from: 'Shanghai'
}

执行上面代码,会出现报错:

//  error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
//  error TS2322: Type '{ [x: string]: string | number; name: string; age: number;from: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

上面的例子中,任意类型的值为 string 类型,name 的值是 string 类型,而 age 的值为 numbernumber 不是 string 的子集,所以会出现报错。

同时,从上面的报错信息中,我们可以看到 

{
    name: 'Lili',
    age: 18,
    from: 'Shanghai'
}

被类型推断为:

{ [x: string]: string | number; name: string; age: number;from: string; }

这就是联合类型 和 接口类型的结合。

🤔问题:上面的例子该如何定义才是正确的呢?

📖答案:可以通过在任意类型中使用联合类型来实现。

比如把上面的例子进行调整🌰:

interface Person {
    name: string;
    age?: number;
    [propName: string]: stringnumber;
}
let Lili:Person = {
    name: 'Lili',
    age: 18,
    from: 'Shanghai'
}

这样就不会出现报错了。

总结:

任意类型即:一个接口的属性可以是任意的类型;一个接口中只能定义一个任意属性;如果接口中有多个类型的属性,则可以在任意属性中使用联合类型。

🤔问题:如果创建的对象中的某个属性,想要只有在创建阶段才能被赋值,该如何设置呢?

这个时候就需要用到接口的另外一个属性:

只读属性

举个简单的例子🌰:

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: stringnumber;
}
let Lili:Person = {
    id: 123
    name: 'Lili',
    age: 18,
    from: 'Shanghai'
}
Lili.id = 666

执行上面的例子,你会发现出现了报错:

//  error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

在 TypeScript 中 使用 readonly 定义 只读属性,上面例子中,定义 id 为只读属性,而后面我们又对这个属性进行了修改,所以会导致报错。

🤔问题:如果在定义变量的时候,并没有去定义这个只读属性,后面再进行赋值,可以吗?

📖答案:不可以。

比如我们对上面的例子进行修改:

interface Person {
    readonly id: number;
    name: string;
}
let Lili:Person = {
    name: 'Lili'
}
Lili.id = 666

执行了之后,你会发现报错了:

// error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
//   Property 'id' is missing in type '{ name: string; }'.
// error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

从上面的报错信息中,可以看到两处错误:

  1. 对 Lili 进行赋值的时候,没有对 id 进行赋值;
  2. Lili.id = 666 对只读属性,进行了赋值,所以会报错;

综上所述:只读属性的约束是在第一次给对象赋值的时候,而不是第一次给只读属性第一次赋值的时候。