本节内容预览
这篇文章,主要会接受下面3个的用法和区别:
- interface 接口;
- class 类;
- type 类型别名;
当我们需要描述一个复杂对象的时候,我们该怎么办?这篇文章会为你展开思路。
先来看个例子
let mySquare: Square = { color: 'white', width: 10 };
例子里我们希望自定义个方块类型 Square ,它有2个属性,颜色和大小。
我们来看下,TS 里这个 Square方块类型 可以怎么定义,顺便可以初步预览下TS中的类和接口。
使用 class 类
class Square {
color: string;
width: number;
constructor (
color: string,
width: number
) {
this.color = color;
this.width = width;
}
}
TS 中的类和 JS 基本写法没有太多区别,无非就是 每个属性上多增加了类型的定义 。其能力其实还是一致的。
使用 interface 接口
interface Square {
color: string;
width: number;
}
我们第一次来看这个接口定义。
如果对比上面 类 的定义来看,也就好理解了。它定义了 Square对象类型 上有2个属性,这2个属性也符合我们例子里的需求。
使用 type 类型别名
type Square = {
color: string,
width: number
}
type就更好理解了,直接等于一个对象类型。
分析下三者的区别
我们可以注意到3种方式都可以完成这个方块类型的定义,但是3者有什么区别呢?我们需要抓住他们的特点:
- class 其实是对象的类的实现,它可以通过
new Spuare()
的方式直接实例化我们需要的对象; - interface 其实更贴近类型定义的作用,它不能直接实例化;
- type 就抓住一个重点,它叫 “别名” ,所以它的作用是为了简化,看定义方式都像一个变量,突出了它的临时性;
那使用的时候我们可以按这么个规则来判断该用那个:
- 如果我们需要构造函数,需要类,需要 实现 那必然是 class;
- 不适用第一条的时候,即我们不需要 实现 ,我们优先考虑 interface是否能满足我们的需求;
- 不适用前2条的时候,我们再使用 type 别名,比如:我们后面会提到的联合类型,使用 type 来减少重复,就是挺常见的办法;
小结和中场休息
通过上面的例子,其实我们已经对 TS 里的类和接口有了一个初步的认识。
熟知 ES中Class的同年甚至可以直接跳过剩下内容。当然继续阅读,然后查漏补缺也是极好的。
类
因为类和JS已有的Class是完全对应的,所以这里再次强调下:本系列重难点是**类型定义,也就是TS和JS不同的地方。**
所以我们的重点就会在以下2个JS没有的地方:
- 属性修饰符
- 抽象类
属性修饰符
先来列举下属性修饰符有哪些:
- 控制是否能被读取:
public
公共、private
私有、protected
保护 - 控制是否能被写:
readonly
只读,这个可以和上面3个同时使用
怎么区分 public、private、protected 的修饰符呢?
先来个例子看下用法:
class Square {
public x: number;
private y: number;
protected z: number;
}
const mySquare = new Square();
写法非常像装饰器,但是无需@;
然后我们对比下,就可以了解了:
- 能 外部访问(
mySquare.x
) — 这个是 public; - 不能外部访问(
mySquare.x
):- 被继承后,能内部访问(
this.y
) — protected; - 被继承后,不能内部访问(
this.y
) — private;
- 被继承后,能内部访问(
一个小注意事项:public 在TS里是默认的,也就是说没有加修饰符的都是 public ** ,你无需特意添加这个修饰符。
readonly 只读修饰符
这个就很好理解了,假如我们为Square的width添加上只读修饰符:
class Square {
color: string;
readonly width: number;
constructor (
color: string,
width: number
) {
this.color = color;
this.width = width; // 完美执行,构造函数里可以赋值
}
}
const mySquare = new Square('red', 10);
mySquare.width = 99; // 错误,width 是只读的
这个看例子就能懂了~ 无需太多解释。
抽象类
抽象类,扶额,又是一个新概念,扶额;
抽象类它是一个专门用来被继承的基类,一般不会被实例化(TS会报错哦)。
它只有1个关键词 abstract ,但是可以用在2个地方,来个例子~
abstract class Shape {
abstract color: string;
abstract getArea (): number;
}
例子中可以看到 abstract 被作用在2个地方:
- 写在 class 前面,说明它是一个抽象类;
- 写在 抽象类的一个成员前 ,且必须在派生类中实现,可以和属性描述符同时使用;
所以我们写的继承的派生类时,可以这么写:
class Square extends Shape {
color: string; // 必须得有 color 属性
width: number;
constructor (
color: string,
width: number
) {
this.color = color;
this.width = width;
}
// 必须得有 getArea 方法
getArea () {
return this.width * this.width;
}
}
同学们会不会问,那写抽象类干嘛?不是自己给自己找难受么?
那约束的作用就是为了让系统更稳定可靠,不会因为忘记写方法或者属性而出错,这个在实现多态等设计时,会非常有用,毕竟有句话说的好:唯有自律才有自由。
接口
TypeScript的核心原则之一是对值所具有的结构进行类型检查。那接口就是用来做这些类型的定义。
interface Square {
color: string;
width: number;
}
在看下这个例子,用来表示一个方块,它有 2个属性,color颜色和width宽度,这个都还好理解。这也是一个基础的接口。
可选属性
继续上面一个例子
interface Square {
color?: string;
width: number;
}
假如我们认为方块的颜色是可选的,不是必须的,那我只需要加一个? 就可以了。
let square1: Square = { width: 10 } // 正确
let square2: Square = { color: '#fff' ,width: 10 } // 正确
看代码就能迅速的知道了,表示 color 它可能有,也可以没有。
只读属性
直接看例子就好,很简单(其实和上文class的只读完全一样)
interface Square {
readonly color: string;
width: number;
}
let square: Square = { color: 'white', width: 10 };
square.width = 20; // 正确
square.color = 'red'; // 错误
关键点:
- 关键词: readonly;
- 只读的只能初始化,不能修改;
同样针对数组,如果想只读可以这么写:
let readOnlyArray: ReadonlyArray<number> = [1,2,3];
readOnlyArray[1] = 5; // 错误
readOnlyArray.push(5); // 错误
readOnlyArray.length = 100; // 错误
let a: number[] = readOnlyArray; // 错误
可以防止对数组的各种修改。
第4种,不知道大家有没理解? 来解释下: 因为js的对象都是引用,ts这样的报错可以防止因为引用的修改,而修改了只读的数组。
那有没可以绕过的呢?有! 类型断言(虽然不推荐使用)
let a: Array[] = readOnlyArray as number[];
可索引类型
先来看下可索引的代码是怎么样的:
interface StringObj {
[key: string]: string;
}
let stringObj: StringObj = { a: 'aaa', b: 'bbb' } // 正确
let stringObj: StringObj = { c: 123 } // 错误
可以看到,它能对所有属性进行约束,但是不会局限于某一个具名的属性。
但是使用时,我们得注意:
- 混用字符串和数字的索引时,数字必须是字符串所以的子类型,看2个例子我们来理解下: ```typescript interface Example1 {
[index: number]: number; // 错误 }
interface Example2 {
[index: number]: ‘a’; // 正确 }
**'a' 是 string的子类型, number和 string是完全不同的东西。**
<a name="bJB8q"></a>
### 额外的属性检查
了解完一系列接口属性的写法,我们看下属性检查怎么做的。<br />额外的属性检查只在 对比类型和实际赋值时校验:
```typescript
interface Square {
color: string;
width: number;
}
let square: Square = { colorX: 'white', width: 10} // 错误
妥妥的报错,也很好理解,毕竟Square中没有colorX只有color
但是有个很有意思的场景:
interface Animal {
name: string;
}
interface Bird {
name: string;
fly: boolean
}
let bird: Bird = { name: 'bbb', fly: true };
let animal: Animal = bird; // 正确
这里却正确了。通过这个例子我们可以了解到,如果赋值都左右2侧都是 已经确定类型都内容,ts其实不是做额外属性检查,而是判断右侧都类型是否左侧都子类型,是否能满足左侧都类型。
本文总结
本篇文章内容还不少,主要是让大家先对可以定义自己类型有个基础的认识,那我们后续可以继续展开一些相对高级的用法。