TypeScriptで継承したinterfaceのプロパティを一部上書きする方法

やっていいかは別として

バグの原因になるとか、OOPに反するとかいろいろあるけれど、interfaceextendsして継承元にあるプロパティを一部だけ上書きして別のプロパティにしたいみたいな気持ちになることがある(と思う)。

おさらい

interface Aがあってそれを継承するinterface Bがあるとする

interface A {
  foo: number
  bar: number
  baz: number
  qux: number
  yo: number
}

interface B extends A {
  cha: string
}

これは普通に通る例

このinterface Bは実質こういう状態

interface B  {
  foo: number
  bar: number
  baz: number
  qux: number
  yo: number
  cha: string
}

OKですね。

プロパティを上書きしたいんだよね

yoプロパティの型を継承した上でB独自にyoだけ型を変更したい。

interface A {
  foo: number
  bar: number
  baz: number
  qux: number
  yo: number
}

interface B extends A {
  cha: string
  yo: string // Aにある"yo"はnumber型だがBではstring型にしたい
}

これできそうな気がするけどやるとエラーになって

インターフェイス 'B' はインターフェイス 'A' を正しく拡張していません。
  プロパティ 'yo' の型に互換性がありません。
    型 'string' を型 'number' に割り当てることはできません。

と言われてしまう。

ハック技で実現できる

中継型を経由すれば実現可能

中継型で上書きしたいプロパティを一度anyにする

interface A {
  foo: number
  bar: number
  baz: number
  qux: number
  yo: number
}

// weakな中継用のinterfaceを用意する
interface Bweaken extends A {
  yo: any
}

interface B extends Bweaken {
  cha: string
  yo: string
}

yoプロパティを変更したいので中継型Bweakenでyoを一度anyにしておけばBでAを継承しつつプロパティを上書きできる

Util

type Weaken<T, K extends keyof T> = {
  [P in keyof T]: P extends K ? any : T[P]
}

こういうのべんり君を用意すると

interface B extends Weaken<A, 'yo'> {
  cha: string
  yo: string
}

便利っぽい。

参考情報

github.com