ひびのログ

日々ではないけどログを出力していくブログ

ライブラリの中で定義しているインターフェースを拡張したかったんですよ

タイトルのとおりで、TypeScript をある程度やっている人なら当然のことかと思いますが。

interface の拡張

TS の interface って拡張できるじゃないですか。

interface Foo {
  bar: string
}
interface Foo {
  baz: number
}

/*
// 結果
Foo: {
  bar: string
  baz: number
}
*/

こんな感じで。

ライブラリの型定義

ライブラリの型定義ってできるじゃないですか。

declare module 'module-name' {
  interface Foo {
    bar: string
  }
  export default Foo
}

import Foo from 'module-name'
/*
// 結果
Foo: {
  bar: string
}
*/

これらを組み合わせる

組み合わせるじゃないですか。

// type definition file
declare module 'module-name' {
  interface Foo {
    bar: string
  }
  export default Foo
}

// application file
import Foo from 'module-name'
declare module 'module-name' {
  interface Foo {
    baz: number
  }
  export default Foo
}
/*
// 結果
Foo: {
  baz: number
}
*/

はい。

どうなってしまったのか

期待値

bar と baz がマージされて、両方ともをメンバーに持つ Foo インターフェースになる。

実際には

あとに定義したほうだけが適用されて、baz をメンバーに持つ Foo インターフェースになった。

どうしたらよかったのか

// type definition file
declare module 'module-name' {
  interface Foo {
    bar: string
  }
  export default Foo
}

// application file
import Foo from 'module-name'
declare module 'module-name' {
  interface Foo {
    baz: number
  }
  // これがいらなかった
  // export default Foo
}
/*
// 結果
Foo: {
  baz: number
}
*/

解説(私見込み)

declare module は namespace のようなものなので、その中で interface を定義した段階でマージされて export までされている状態になっているっぽいです。

無駄に export してしまったため、違うものと認識され(?)、後に定義したものが先に export したものを上書きしてしまったっぽいです。

まとめ

公式を見るというのと、ミニマル環境で納得できるまで試してみて、ちゃんと理解することが大事なんだなと再確認しましたね。

正直型定義ファイルは declare module 'module-name' だけできれば最低限コードは書けるので、理解を後回しにしていたのが仇になりました。

まぁ今回完全に理解できたので余裕だわ!1111111

ちなみに

vue-property-decorator の Vue を拡張したくて。

Element-UI を入れたはいいものの、型定義がなくて this.$message が使えなかったんですよね。