はじめに (対象読者・この記事でわかること)

この記事は、Vue.js と TypeScript を組み合わせて開発しているフロントエンドエンジニア、あるいはこれから学び始めようと考えている方を対象としています。
extends Vue は TypeScript の機能なのか?」という疑問に対して、Vue が提供する継承メカニズムと TypeScript のクラス継承をそれぞれ整理し、実際に使えるコード例を交えて解説します。

この記事を読むことで、以下ができるようになります。

  • Vue のオプション API における Vue.extendextends オプションの違いを理解する
  • TypeScript でクラスベースのコンポーネントを作成し、extends Vue が何を意味するかを把握する
  • 継承を利用したコンポーネント設計のベストプラクティスと、よくあるエラーの対処法を身につける

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。
基本的な JavaScript(ES6)の文法とモジュールシステム
Vue.js(2 系または 3 系)のコンポーネント構造とオプション API の概要
* TypeScript の基本的な型付けとクラス構文

Vue の継承メカニズムと TypeScript の extends(概要・背景)

Vue には大きく分けて二つの「継承」手法があります。

  1. 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 と指定して継承できます。

  2. extends オプション(オブジェクトマージ)
    Vue のコンポーネントオプションオブジェクトに extends プロパティを設定すると、指定したオプションがマージされます。
    js export default { extends: BaseComponent, data() { return { extra: 42 }; } };
    ここでの extendsVue が独自に解釈するキー であり、TypeScript のキーワードとは無関係です。

一方、TypeScript の extends はクラス継承を意味するキーワードです。

Ts
class Child extends Parent { // ... }

Vue 3 以降で公式に提供されている @vue/runtime-core の型定義により、Vue クラス自体がエクスポートされ、class MyComponent extends Vue {} と書くことが可能になります。これが「extends Vue は TypeScript の機能ですか?」という質問への直接的な答えです。

結論として、
Vue.extendextends オプションは Vue の機能
class X extends VueTypeScript のクラス継承 を利用した 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>

この例で見ると、ChildComponentBaseComponentクラス継承(TypeScript の extends)で受け取っており、this.countincrement といった親クラスのプロパティ・メソッドがそのまま使えます。

ステップ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.jsontypesvue が含まれていない。

解決策:以下を実行して依存を追加し、tsconfig.json"types": ["vue"] を追記。

Bash
npm i -D vue-class-component

エラー 2:Property 'increment' does not exist on type 'ComponentPublicInstance<…>'

原因:テンプレート内で increment を呼び出しているが、コンポーネントの型推論が子クラスのメソッドを認識していない。

解決策defineComponent で明示的に型を拡張するか、ComponentPublicInstance のジェネリクスに BaseComponent を指定する。

Ts
import { ComponentPublicInstance } from 'vue'; type MyComponent = ComponentPublicInstance<BaseComponent>; export default { // ... } as MyComponent;

エラー 3:extends オプションと class extends が同時に使われてコンフリクト

原因@Options デコレータ内で extends キーを誤って記述したため、Vue がオプション継承とクラス継承を同時に解釈しようとした。

解決策:クラスベースの場合は @Optionsextends を記述しない。必要な継承は TypeScript の extends だけで完結する。

実装上のベストプラクティス

  1. 型情報は必ず付ける
    クラスフィールドやメソッドに型アノテーションを入れることで、IDE の補完が有効になりミスが減ります。

  2. super の呼び出しを忘れない
    ライフサイクルフックやオーバーライドメソッドを上書きする際は、super.methodName() で親側のロジックを明示的に呼び出すと予期せぬ挙動を防げます。

  3. 継承は浅く保つ
    多段階のクラス継承は TypeScript の型推論を複雑にし、デバッグが困難になるため、可能な限りミックスインやコンポジション API の利用を検討してください。

  4. オプション API とクラスベースは混在させない
    同一コンポーネントで @Optionsextends オプションを併用すると、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 の型推論を組み合わせた高度なコンポーネント設計について解説する予定です。

参考資料