Typescript学习(八)结构化类型和它所带来的问题

68 阅读3分钟

何为结构化类型系统

我们一直都说, Typescript是结构化类型系统, 或者说所谓的鸭式辨型法, 即 一个东西长得像鸭子, 那它就是鸭子, 而不在乎它实际名字叫什么;

class Duck {
  legs:number = 2
  run () {
    console.log('I can run')
  }
}

class Chicken {
  legs:number = 2
  run () {
    console.log('I can run')
  }
}

function getADuckLegs (duck:Duck):number {
  return duck.legs
}

getADuckLegs(new Duck())
getADuckLegs(new Chicken())

上面的例子中, 我们定义了一个Duck和一个Chicken类, 两者结构相同, 都是有legs属性和run方法, 那么, 在Typescript中, 它俩就是一个类型, 我们无需去纠结于这个类的标识符是什么, 只要我两个类型的数据结构一致, 那它就是相同的类, 结构化类型系统有利于让我们编写出更加通用/可复用/可维护的代码, 但是也带来了一些困扰, 比如上面的案例中, 我们要的是一只鸭子的腿的数量, 可我们传入'一只鸡', 代码似乎也没毛病, 如果遇到这类问题, 我们又需要区分, 那怎么办?

利用访问修饰符区分

首先我们可以利用访问修饰符, 在Typescript中, private和protected具有特殊的访问权限, 两个类即使拥有相同的结构, 但是, 如果其内部属性包含private和protected, 它们仍会被认定为不同的类型!

class Duck {
  protected _id = ''
  legs:number = 2
  run () {
    console.log('I can run')
  }
}

class Chicken {
  protected _id = ''
  legs:number = 2
  run () {
    console.log('I can run')
  }
}

function getADuckLegs (duck:Duck):number {
  return duck.legs
}

getADuckLegs(new Duck())
getADuckLegs(new Chicken()) // 报错

因为我们增加了一个受保护的属性_id, 就导致这两个类产生了差异, 从而免于两个类被归为一个类型;

利用泛型区分

上一个例子很简单, 但是, 也有一定的问题, 那就是我们必须要去在具体的类上定一个压根儿不需要用到的_id, 那如果我们有很多个这种类需要区分, 岂不是每个类都要添加这种多余的代码? 我们是否能够将这种用于区分类型的逻辑拆分出来呢? 接着往下看

class Duck {
  legs:number = 2
  run () {
    console.log('I can run')
  }
}

class Chicken {
  legs:number = 2
  run () {
    console.log('I can run')
  }
}

// 定义一个用于区分的类
class Diff <T extends string>{
  protected id?:T
}

// 定义一个工具类型, 接受俩泛型
type DiffType<T, U extends string> = T & Diff<U>

// 分别得出不同的类型
type DuckType = DiffType<Duck, 'duck'>
type ChickenType = DiffType<Chicken, 'chicken'>

const duckInstance = new Duck() as DuckType
const chickenInstance = new Chicken() as ChickenType

function getADuckLegs (duck:DuckType):number {
  return duck.legs
}

getADuckLegs(duckInstance)
getADuckLegs(chickenInstance) // 报错

这里, 我们将区分类型的逻辑, 独立成了一个Diff类它接受一个泛型, 这个泛型可以改变其属性的类型, 这里, 我们就埋下了一个"可能造成不同的点", 再由此声明了一个工具类型DiffType, 它接受两个泛型参数, 其中一个给到Diff类的泛型参数中, 得到一个新的类型, 然后再和另一个泛型参数求交叉类型. 然后再用DiffType创建出两个不同的类型: DuckType和ChickenType, 让Duck和Chincken的实例分别断言成这两个类型, 接着, 我们就可以使用DuckType和ChickenType取代原有的Duck和Chicken类型了!

小节: 这里的核心在于, 将一个类的成员的属性变为一个可以被改变的变量, 由此人为创造出'不同', 然后再将利用交叉类型的特性, 既保留了Duck和Chicken这两个类的属性成员, 又继承了Diff的'不同', 从而将两个原本结构相同的类型, 区分开来了;