はじめに (対象読者・この記事でわかること)
この記事は、Vue.js と TypeScript を組み合わせて開発しているフロントエンドエンジニア、あるいはこれから学び始めようと考えている方を対象としています。
「extends Vue は TypeScript の機能なのか?」という疑問に対して、Vue が提供する継承メカニズムと TypeScript のクラス継承をそれぞれ整理し、実際に使えるコード例を交えて解説します。
この記事を読むことで、以下ができるようになります。
- Vue のオプション API における
Vue.extendとextendsオプションの違いを理解する - TypeScript でクラスベースのコンポーネントを作成し、
extends Vueが何を意味するかを把握する - 継承を利用したコンポーネント設計のベストプラクティスと、よくあるエラーの対処法を身につける
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
基本的な JavaScript(ES6)の文法とモジュールシステム
Vue.js(2 系または 3 系)のコンポーネント構造とオプション API の概要
* TypeScript の基本的な型付けとクラス構文
Vue の継承メカニズムと TypeScript の extends(概要・背景)
Vue には大きく分けて二つの「継承」手法があります。
-
Vue.extend(オプション API)
Vue 2 系で主に使用された手法で、コンポーネントオプションオブジェクトを受け取り、new Vue()と同様の機能を持つ「拡張コンストラクタ」を生成します。
js const BaseComponent = Vue.extend({ data() { return { message: 'Hello' }; }, methods: { greet() { console.log(this.message); } } });
生成されたコンストラクタはnew BaseComponent()でインスタンス化でき、他のコンポーネントでextends: BaseComponentと指定して継承できます。 -
extendsオプション(オブジェクトマージ)
Vue のコンポーネントオプションオブジェクトにextendsプロパティを設定すると、指定したオプションがマージされます。
js export default { extends: BaseComponent, data() { return { extra: 42 }; } };
ここでのextendsは Vue が独自に解釈するキー であり、TypeScript のキーワードとは無関係です。
一方、TypeScript の extends はクラス継承を意味するキーワードです。
Tsclass Child extends Parent { // ... }
Vue 3 以降で公式に提供されている @vue/runtime-core の型定義により、Vue クラス自体がエクスポートされ、class MyComponent extends Vue {} と書くことが可能になります。これが「extends Vue は TypeScript の機能ですか?」という質問への直接的な答えです。
結論として、
Vue.extend と extends オプションは Vue の機能
class X extends Vue は TypeScript のクラス継承 を利用した Vue のクラスベースコンポーネント です。
クラスベースコンポーネントの実装手順と実例
ここでは、Vue 3 + TypeScript 環境で「extends Vue」を用いたクラスベースコンポーネントを作成し、オプション API の継承と比較しながら実装手順を解説します。
ステップ1 プロジェクトの作成と基本設定
Bash# Vue CLI で TypeScript プロジェクトを作成 npm init vue@latest my-vue-app # プロンプトで「TypeScript」を選択 cd my-vue-app npm install
src/shims-vue.d.ts が自動生成され、Vue の型がインポート可能になります。
ステップ2 ベースクラスの定義(BaseComponent.ts)
Ts// src/components/BaseComponent.ts import { Options, Vue } from 'vue-class-component'; @Options({ // オプション API と同様に定義可能 props: { baseMessage: { type: String, default: 'Base' } } }) export default class BaseComponent extends Vue { // データはクラスフィールドとして宣言 count = 0; // メソッドはそのまま記述 increment() { this.count++; console.log(`${this.baseMessage}: ${this.count}`); } // ライフサイクルフックもメソッドで記述 mounted() { console.log('BaseComponent mounted'); } }
ポイントは vue-class-component の @Options デコレータを利用し、従来のオプション API と同等の設定を行える点です。
ステップ3 サブコンポーネントで継承(ChildComponent.vue)
Vue<template> <div> <p>{{ baseMessage }} - 子コンポーネントです</p> <p>カウント: {{ count }}</p> <button @click="increment">インクリメント</button> <button @click="childMethod">子だけの処理</button> </div> </template> <script lang="ts"> import { Options } from 'vue-class-component'; import BaseComponent from '@/components/BaseComponent'; @Options({}) export default class ChildComponent extends BaseComponent { // 子クラスで新たに定義するデータ extra = 'extra data'; // 子クラス独自のメソッド childMethod() { console.log('子コンポーネントのメソッド', this.extra); } // 必要に応じてライフサイクルフックを上書き mounted() { super.mounted(); // 親の mounted を呼び出す console.log('ChildComponent mounted'); } } </script> <style scoped> button { margin-right: 8px; } </style>
この例で見ると、ChildComponent は BaseComponent を クラス継承(TypeScript の extends)で受け取っており、this.count や increment といった親クラスのプロパティ・メソッドがそのまま使えます。
ステップ4 オプション API での継承との比較
同じ機能をオプション API で実装すると次のようになります。
Js// BaseComponent.js export const BaseComponent = { props: { baseMessage: String }, data() { return { count: 0 }; }, methods: { increment() { this.count++; console.log(`${this.baseMessage}: ${this.count}`); } }, mounted() { console.log('BaseComponent mounted'); } };
Vue<!-- ChildComponent.vue --> <template>…</template> <script> import { BaseComponent } from '@/components/BaseComponent'; export default { extends: BaseComponent, data() { return { extra: 'extra data' }; }, methods: { childMethod() { console.log('子コンポーネントのメソッド', this.extra); } }, mounted() { // 親の mounted は自動的に呼び出される console.log('ChildComponent mounted'); } }; </script>
主な相違点
| 項目 | クラスベース(TypeScript) | オプション API |
|------|---------------------------|----------------|
| 継承方法 | class Child extends BaseComponent(TypeScript のキーワード) | extends: BaseComponent(Vue のオプション) |
| 型安全性 | TypeScript がコンパイル時に型チェック | ランタイムでのみチェック |
| 可読性 | 大規模コードでクラス階層が明確に見える | オブジェクトのマージが中心で階層が見えにくい |
| デコレータ | @Options で Vue のライフサイクルや props を宣言 | 直接オブジェクトに記述 |
ハマった点やエラー解決
エラー 1:Cannot find name 'Vue'
原因:vue-class-component がインストールされていない、もしくは tsconfig.json の types に vue が含まれていない。
解決策:以下を実行して依存を追加し、tsconfig.json に "types": ["vue"] を追記。
Bashnpm i -D vue-class-component
エラー 2:Property 'increment' does not exist on type 'ComponentPublicInstance<…>'
原因:テンプレート内で increment を呼び出しているが、コンポーネントの型推論が子クラスのメソッドを認識していない。
解決策:defineComponent で明示的に型を拡張するか、ComponentPublicInstance のジェネリクスに BaseComponent を指定する。
Tsimport { ComponentPublicInstance } from 'vue'; type MyComponent = ComponentPublicInstance<BaseComponent>; export default { // ... } as MyComponent;
エラー 3:extends オプションと class extends が同時に使われてコンフリクト
原因:@Options デコレータ内で extends キーを誤って記述したため、Vue がオプション継承とクラス継承を同時に解釈しようとした。
解決策:クラスベースの場合は @Options に extends を記述しない。必要な継承は TypeScript の extends だけで完結する。
実装上のベストプラクティス
-
型情報は必ず付ける
クラスフィールドやメソッドに型アノテーションを入れることで、IDE の補完が有効になりミスが減ります。 -
superの呼び出しを忘れない
ライフサイクルフックやオーバーライドメソッドを上書きする際は、super.methodName()で親側のロジックを明示的に呼び出すと予期せぬ挙動を防げます。 -
継承は浅く保つ
多段階のクラス継承は TypeScript の型推論を複雑にし、デバッグが困難になるため、可能な限りミックスインやコンポジション API の利用を検討してください。 -
オプション API とクラスベースは混在させない
同一コンポーネントで@Optionsとextendsオプションを併用すると、Vue がオプションマージを二重に行い予期せぬ結果になることがあります。
まとめ
本記事では、Vue の継承手法と TypeScript の extends キーワードの違いを整理し、実際に クラスベースコンポーネント を作成する手順とオプション API との比較を行いました。
- Vue の
extendsは Vue 独自のオプションマージ、Vue.extendはコンストラクタ生成 class X extends Vueは TypeScript のクラス継承であり、vue-class-componentに依存- クラスベースは型安全性と可読性が高いが、過度な継承は避け、コンポジション API の併用を検討
これにより、読者は「extends Vue が何を指すのか」を正しく理解し、プロジェクトに最適な継承戦略を選択できるようになります。次回は、Vue 3 の Composition API と TypeScript の型推論を組み合わせた高度なコンポーネント設計について解説する予定です。
参考資料
- Vue.js 公式ドキュメント – コンポーネント継承
- vue-class-component GitHub リポジトリ
- TypeScript Handbook – Classes
- 「Vue.js実践入門」(技術評論社) – 第5章 コンポーネント設計