本文是对TypeScript类型系统系列之基本篇的补充和实战,传送门。
咱们先从接口聊起
接口
应用1
如何定义接口类型为可调用?(类比Java8里的函数式接口),如下:
interface ActionById {
(id: number): string
}
const testActionById = () => {
const getActionById: ActionById = (id: number) => 'delete';
const result = getActionById(1);
}
应用2
如何定义对象的成员数量不确定的属性,使用可索引类型表示。这个语言特性特别实用,在下篇的内容提到的索引类型,会看到类似的语法。
interface Phone {
[name: string]: string
}
这时候,Phone类型表示任意多个字段名为string,字段值类型为string的字段。
应用3
接口必须和类搭配使用,通常通过接口能规范的只是类的实例方法和属性,TS还可以规范类的构造方法。
interface TaskConstructor {
new (name: string , taskStatus: number);
}
const task: TaskConstructor = class Task {
name: string;
taskStatus: number;
createTime: string;
taskType: number;
constructor(name: string , taskStatus: number){
}
}
class类定义返回的就是该类构造方法的类型,这个特性让类的实例可组合,比如
interface TaskConstructor {
new (name: string , taskStatus: number): ITask;
}
interface ITask {
name: string
taskStatus: number
}
class TaskA implements ITask{
name: string;
taskStatus: number;
constructor(name: string , taskStatus: number){
console.log('generate TaskA');
}
}
class TaskB implements ITask{
name: string;
taskStatus: number;
createTime: string;
taskType: number;
constructor(name: string , taskStatus: number){
console.log('create TaskB');
}
}
const createTask = (task: TaskConstructor, name: string , taskStatus: number) => (new task(name, taskStatus))
const taskA: ITask = createTask(TaskA, 'A', 1);
const taskB: ITask = createTask(TaskB, 'B', 2);
上面createTask方法,让Task实例变得可动态化和可配置化。对类的实例化这个动作和实例的表示在代码的空间和时序上做了分离。
联合类型
联合类型、字面量类型/枚举的综合使用
给出一个场景:某个类型,字段A是否存在依赖于另一个字段B的值
比如下面定义OperationAction类型,当status字段值是‘add’时,存在addId字段,不存在removeId字段,当status字段值是‘remove’时,只存在removeId字段,而没有addId字段,怎么改进OperationAction类型?
type OperationAction = {
status: 'add' | 'remove',
taskName: string,
addId?: number,
removeId?: number,
}
OperationAction2是对OperationAction的改进
type OperationAction2 = {
status: 'add',
taskName: string,
addId: number,
} | {
status: 'remove',
taskName: string,
removeId: number,
}
我们把status字段的联合类型操作符|移动到了外层。使用时如这般:
const assertNever = (x: never): never => {
throw new Error("Unexpected: " + x);
}
const testOperationAction2 = (action: OperationAction2) => {
switch(action.status){
case 'add':
const aid = action.addId;
break;
case 'remove':
const rid = action.removeId;
break;
default:
assertNever(action);
}
}
这里status字段除了使用字面量类型外,还可以使用枚举,同样能实现addId字段和removeId字段通过status字段值来区别
enum ActionType {
Add,
Remove,
}
type OperationAction3 = {
status: ActionType.Add,
taskName: string,
addId: number,
} | {
status: ActionType.Remove,
taskName: string,
removeId: number,
}
const assertNever = (x: never): never => {
throw new Error("Unexpected: " + x);
}
const testOperationAction3 = (action: OperationAction3) => {
switch(action.status){
case ActionType.Add:
const aid = action.addId;
break;
case ActionType.Remove:
const rid = action.removeId;
break;
default:
assertNever(action);
}
}
上面所说的是,类型里的字段A的有无依赖另一个字段,如果是字段A的类型(比如string或者number),取决另一个字段B的值呢?同理,可以通过上述方法实现。
小结
TS的接口比Java 8的接口强大,额外多了可索引类型表示,直接可调用,构造函数可实例等诸多特性。
联合类型在使用是在TS里高频使用,大大增加了代码的类型动态化。
TS类型系列延伸阅读:
Ethan Ruan:TypeScript类型系统(3/5)- 进阶篇zhuanlan.zhihu.com