Vue.jsの単一ファイルコンポーネント(SFC)徹底解説

2025-07-29

単一ファイルコンポーネントとは

単一ファイルコンポーネント(Single File Component、以下SFC)は、Vue.jsのコンポーネントを.vue拡張子の1つのファイルにまとめる仕組みです。1つのファイルにテンプレート(HTML)、ロジック(JavaScript)、スタイル(CSS)をカプセル化できます。

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.example {
  color: red;
}
</style>

SFCの基本構造

SFCは3つの主要なセクションで構成されます。

1. Templateセクション

<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ content }}</p>
    <button @click="handleClick">クリック</button>
  </div>
</template>

2. Scriptセクション

<script>
export default {
  name: 'MyComponent',
  data() {
    return {
      title: 'コンポーネントのタイトル',
      content: 'これはコンポーネントの内容です'
    }
  },
  methods: {
    handleClick() {
      alert('ボタンがクリックされました');
    }
  }
}
</script>

3. Styleセクション

<style scoped>
h1 {
  color: #42b983;
}
button {
  padding: 8px 16px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
}
</style>

SFCの利点

  1. 関心の分離:関連するコードを1つのファイルにまとめながら、論理的なセクションに分離
  2. スコープ付きCSSscoped属性でコンポーネント専用のスタイルを定義可能
  3. プリプロセッササポート:TypeScript、SCSS、Pugなど様々なプリプロセッサを使用可能
  4. モジュール化:コンポーネントを簡単にインポート/エクスポート可能
  5. 開発体験の向上:IDEやツールのサポートが充実

コンポーネントの登録と使用

グローバル登録

// main.js
import Vue from 'vue';
import MyComponent from './MyComponent.vue';

Vue.component('MyComponent', MyComponent);

new Vue({
  el: '#app'
});

ローカル登録

<script>
import MyComponent from './MyComponent.vue';

export default {
  components: {
    MyComponent
  }
}
</script>

プロップスの使用

親コンポーネントから子コンポーネントへデータを渡します。

<!-- ParentComponent.vue -->
<template>
  <ChildComponent :message="parentMessage" />
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      parentMessage: '親からのメッセージ'
    }
  }
}
</script>
<!-- ChildComponent.vue -->
<template>
  <div>{{ message }}</div>
</template>

<script>
export default {
  props: {
    message: {
      type: String,
      required: true,
      default: 'デフォルトメッセージ'
    }
  }
}
</script>

カスタムイベントの使用

子コンポーネントから親コンポーネントへイベントを発行します。

<!-- ChildComponent.vue -->
<template>
  <button @click="notifyParent">親に通知</button>
</template>

<script>
export default {
  methods: {
    notifyParent() {
      this.$emit('child-event', '子からのデータ');
    }
  }
}
</script>
<!-- ParentComponent.vue -->
<template>
  <ChildComponent @child-event="handleChildEvent" />
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  methods: {
    handleChildEvent(data) {
      console.log('子コンポーネントから:', data);
    }
  }
}
</script>

スロットの使用

親コンポーネントから子コンポーネントへコンテンツを渡します。

<!-- ChildComponent.vue -->
<template>
  <div class="container">
    <h2>タイトル</h2>
    <slot>デフォルトコンテンツ</slot>
  </div>
</template>
<!-- ParentComponent.vue -->
<template>
  <ChildComponent>
    <p>ここがスロットに挿入されるコンテンツです</p>
  </ChildComponent>
</template>

名前付きスロット

複数のスロットを定義する場合に使用します。

<!-- BaseLayout.vue -->
<template>
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>
<!-- ParentComponent.vue -->
<template>
  <BaseLayout>
    <template v-slot:header>
      <h1>ヘッダーコンテンツ</h1>
    </template>

    <p>メインコンテンツ</p>

    <template v-slot:footer>
      <p>フッターコンテンツ</p>
    </template>
  </BaseLayout>
</template>

動的コンポーネント

<component>タグとis属性で動的にコンポーネントを切り替えられます。

<template>
  <div>
    <button @click="currentComponent = 'ComponentA'">Aを表示</button>
    <button @click="currentComponent = 'ComponentB'">Bを表示</button>

    <component :is="currentComponent"></component>
  </div>
</template>

<script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

export default {
  components: {
    ComponentA,
    ComponentB
  },
  data() {
    return {
      currentComponent: 'ComponentA'
    }
  }
}
</script>

ミックスインの使用

複数のコンポーネントでロジックを共有できます。

// myMixin.js
export default {
  data() {
    return {
      mixinData: 'ミックスインのデータ'
    }
  },
  methods: {
    mixinMethod() {
      console.log('ミックスインのメソッド');
    }
  }
}
<!-- MyComponent.vue -->
<script>
import myMixin from './myMixin.js';

export default {
  mixins: [myMixin],
  created() {
    console.log(this.mixinData); // ミックスインのデータ
    this.mixinMethod(); // ミックスインのメソッド
  }
}
</script>

カスタムディレクティブ

コンポーネント内でカスタムディレクティブを定義できます。

<template>
  <div v-highlight="'yellow'">このテキストはハイライトされます</div>
</template>

<script>
export default {
  directives: {
    highlight: {
      inserted(el, binding) {
        el.style.backgroundColor = binding.value;
      }
    }
  }
}
</script>

コンポジションAPIの使用

Vue 3のComposition APIをSFCで使用する例です。

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">増やす</button>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);

    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment
    };
  }
}
</script>

テストの容易性

SFCはテストが書きやすい構造です。

// MyComponent.spec.js
import { shallowMount } from '@vue/test-utils';
import MyComponent from './MyComponent.vue';

describe('MyComponent', () => {
  it('クリック時にイベントを発行する', () => {
    const wrapper = shallowMount(MyComponent);
    wrapper.find('button').trigger('click');
    expect(wrapper.emitted('click-event')).toBeTruthy();
  });
});

ベストプラクティス

  1. コンポーネント命名規則:パスカルケース(例: MyComponent.vue
  2. 単一責任の原則:1コンポーネントにつき1つの責務
  3. 適切な分割:大きくなりすぎたコンポーネントは分割
  4. propsバリデーション:propsの型と必須チェックを実装
  5. scopedスタイル:スタイルの衝突を防ぐ
  6. ドキュメンテーション:props、events、slotsをコメントで説明
<!-- 
@component MyButton
@desc カスタムボタンコンポーネント
@prop {String} type - ボタンのタイプ (primary|secondary)
@prop {Boolean} disabled - 無効状態
@event click - クリックイベント
@slot default - ボタン内のコンテンツ
-->
<template>
  <button 
    :class="['my-button', type]"
    :disabled="disabled"
    @click="$emit('click')"
  >
    <slot></slot>
  </button>
</template>

<script>
export default {
  name: 'MyButton',
  props: {
    type: {
      type: String,
      default: 'primary',
      validator: value => ['primary', 'secondary'].includes(value)
    },
    disabled: {
      type: Boolean,
      default: false
    }
  }
}
</script>

<style scoped>
.my-button {
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
}
.my-button.primary {
  background-color: #42b983;
  color: white;
}
.my-button.secondary {
  background-color: #f0f0f0;
  color: #333;
}
.my-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>

まとめ

Vue.jsの単一ファイルコンポーネント(SFC)は、現代的なフロントエンド開発に最適化された強力な機能です。.vueファイルにテンプレート、スクリプト、スタイルをまとめることで、コンポーネントの開発とメンテナンスが容易になります。

  • 関心の分離:関連するコードを1つのファイルにまとめつつ、論理的に分離
  • 再利用性:コンポーネントを簡単にインポート/エクスポート可能
  • スコープ付きスタイルscoped属性でスタイルの衝突を防止
  • ツールサポート:Vue CLIやViteなどのツールとシームレスに統合
  • テスト容易性:コンポーネント単位でテスト可能

SFCを適切に活用することで、より整理された、メンテナンス性の高いVue.jsアプリケーションを構築できます。