
Vue.js コンポーネント演習問題
2025-07-29初級問題 (6問)
問題1
単一ファイルコンポーネント(.vueファイル)の基本的な構造を説明し、3つの主要セクションをコードで示してください。
問題2
以下のコンポーネントを単一ファイルコンポーネントとして完成させてください。コンポーネント名はButtonCounter
で、クリックするとカウントが増えるボタンです。
<template>
<!-- ここにテンプレートを記述 -->
</template>
<script>
<!-- ここにスクリプトを記述 -->
</script>
<style>
<!-- ここにスタイルを記述 -->
</style>
問題3
親コンポーネントから子コンポーネントにmessage
というデータをpropsで渡すコードを書いてください。親コンポーネントのデータは"Hello Vue!"
、子コンポーネントはこのメッセージを表示します。
問題4
子コンポーネントから親コンポーネントにイベントを発行するコードを書いてください。子コンポーネントにはボタンがあり、クリックするとincrement
イベントを発行します。親コンポーネントはこのイベントを受け取ってカウンターを増やします。
問題5
以下の単一ファイルコンポーネントの間違いを指摘し、修正してください。
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</div>
</template>
<script>
export default {
data: {
title: 'こんにちは',
content: 'Vue.jsの世界へようこそ'
}
}
</script>
問題6
コンポーネントにローカルスタイルを適用する際に、スタイルが他のコンポーネントに影響を与えないようにする方法をコードで示してください。
中級問題 (12問)
問題7
動的コンポーネントの使い方を示すコードを書いてください。2つのコンポーネント(ComponentA
とComponentB
)を切り替えるボタンを作成してください。
問題8
スロットを使用して、親コンポーネントから子コンポーネントCard
にコンテンツを渡すコードを書いてください。Cardコンポーネントはヘッダー、本文、フッターの3つのスロットを持っています。
問題9
propsのバリデーションを設定した子コンポーネントを作成してください。age
というpropは数値で必須、18以上である必要があります。
問題10
親子コンポーネント間で双方向バインディングを実現するコードを書いてください。子コンポーネントはv-model
に対応した入力コンポーネントです。
問題11
非同期コンポーネントの登録方法を示すコードを書いてください。AsyncComponent
は必要になったときにのみ読み込まれるようにしてください。
問題12
子コンポーネントでv-for
を使用してリストを表示し、各アイテムに一意のキーを設定するコードを書いてください。親コンポーネントからアイテムの配列をpropsで受け取ります。
問題13
コンポジションAPIを使用した単一ファイルコンポーネントの例を書いてください。counter
というリアクティブなデータと、それを増減するメソッドを含めてください。
問題14
provide/injectを使用して、祖先コンポーネントから深くネストされた子コンポーネントにデータを渡すコードを書いてください。テーマ情報(theme: 'dark'
)を渡します。
問題15
カスタムイベントを使用して、孫コンポーネントから祖父母コンポーネントにイベントを伝播させるコードを書いてください。イベント名はnotify
で、メッセージを含みます。
問題16
子コンポーネントのルート要素にrefを設定し、親コンポーネントからその要素にアクセスするコードを書いてください。
問題17
関数型コンポーネントの例を作成してください。FunctionalButton
というコンポーネントで、クリックイベントを親に伝達します。
問題18
コンポーネントのライフサイクルフックを使用して、コンポーネントがマウントされた時と更新された時にコンソールにログを出力するコードを書いてください。
上級問題 (6問)
問題19
動的スロット名を使用した高度なスロットの使い方を示すコードを書いてください。親コンポーネントから動的にスロット名を決定して子コンポーネントにコンテンツを渡します。
問題20
レンダー関数を使用して単一ファイルコンポーネントを作成してください。h1
要素とp
要素を含むシンプルなコンポーネントにしてください。
問題21
カスタムディレクティブを単一ファイルコンポーネントで使用する例を示してください。要素をクリックしたときに背景色が変わるv-highlight
ディレクティブを作成してください。
問題22
プラグインとして利用可能なコンポーネントを作成してください。このコンポーネントはVue.use()で登録でき、グローバルコンポーネントとして使用できます。
問題23
スコープ付きスロットを使用して、子コンポーネントのデータを親コンポーネントのテンプレートで使用するコードを書いてください。子コンポーネントはアイテムのリストを持ち、親はその表示方法を制御します。
問題24
Vue 3のTeleport機能を使用して、モーダルコンポーネントをbody要素の直接の子としてレンダリングするコードを書いてください。
解答例
初級解答
解答1
<template>
<!-- HTMLテンプレート -->
<div>{{ message }}</div>
</template>
<script>
<!-- JavaScriptロジック -->
export default {
data() {
return {
message: 'Hello Vue!'
}
}
}
</script>
<style>
<!-- CSSスタイル -->
div {
color: blue;
}
</style>
解答2
<template>
<button @click="count++">Clicked {{ count }} times</button>
</template>
<script>
export default {
name: 'ButtonCounter',
data() {
return {
count: 0
}
}
}
</script>
<style>
button {
padding: 8px 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
}
</style>
解答3
親コンポーネント:
<template>
<ChildComponent :message="parentMessage" />
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: { ChildComponent },
data() {
return {
parentMessage: 'Hello Vue!'
}
}
}
</script>
子コンポーネント:
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
props: ['message']
}
</script>
解答4
子コンポーネント:
<template>
<button @click="$emit('increment')">Increment</button>
</template>
親コンポーネント:
<template>
<div>
<p>Count: {{ count }}</p>
<ChildComponent @increment="count++" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: { ChildComponent },
data() {
return {
count: 0
}
}
}
</script>
解答5
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</div>
</template>
<script>
export default {
data() { // dataは関数にする必要がある
return {
title: 'こんにちは',
content: 'Vue.jsの世界へようこそ'
}
}
}
</script>
解答6
<style scoped>
/* scoped属性を追加することでスタイルのスコープを限定 */
h1 {
color: red;
}
</style>
中級解答
解答7
<template>
<div>
<button @click="currentComponent = 'ComponentA'">Show A</button>
<button @click="currentComponent = 'ComponentB'">Show 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>
解答8
子コンポーネント (Card.vue):
<template>
<div class="card">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- デフォルトスロット -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
親コンポーネント:
<template>
<Card>
<template v-slot:header>
<h2>タイトル</h2>
</template>
<p>メインコンテンツ</p>
<template v-slot:footer>
<button>詳細</button>
</template>
</Card>
</template>
解答9
<template>
<div v-if="age !== null">
Age: {{ age }}
</div>
</template>
<script>
export default {
props: {
age: {
type: Number,
required: true,
validator: value => value >= 18
}
}
}
</script>
解答10
子コンポーネント:
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
親コンポーネント:
<template>
<CustomInput v-model="message" />
<p>Message: {{ message }}</p>
</template>
<script>
import CustomInput from './CustomInput.vue'
export default {
components: { CustomInput },
data() {
return {
message: ''
}
}
}
</script>
解答11
const AsyncComponent = () => ({
component: import('./AsyncComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
})
export default {
components: {
AsyncComponent
}
}
解答12
子コンポーネント:
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true
}
}
}
</script>
解答13
<template>
<div>
<p>Count: {{ counter }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const counter = ref(0)
const increment = () => counter.value++
const decrement = () => counter.value--
return {
counter,
increment,
decrement
}
}
}
</script>
解答14
祖先コンポーネント:
<template>
<ParentComponent />
</template>
<script>
import { provide } from 'vue'
import ParentComponent from './ParentComponent.vue'
export default {
components: { ParentComponent },
setup() {
provide('theme', 'dark')
}
}
</script>
子孫コンポーネント:
<template>
<div :class="theme">
<!-- コンテンツ -->
</div>
</template>
<script>
import { inject } from 'vue'
export default {
setup() {
const theme = inject('theme', 'light') // デフォルト値 'light'
return {
theme
}
}
}
</script>
解答15
孫コンポーネント:
<template>
<button @click="notifyGrandparent">Notify</button>
</template>
<script>
export default {
methods: {
notifyGrandparent() {
this.$emit('notify', 'Hello from grandchild!')
}
}
}
</script>
子コンポーネント:
<template>
<GrandchildComponent @notify="$emit('notify', $event)" />
</template>
親コンポーネント:
<template>
<ChildComponent @notify="handleNotify" />
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: { ChildComponent },
methods: {
handleNotify(message) {
console.log(message) // "Hello from grandchild!"
}
}
}
</script>
解答16
子コンポーネント:
<template>
<div ref="root">Child Component</div>
</template>
親コンポーネント:
<template>
<ChildComponent ref="child" />
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: { ChildComponent },
mounted() {
console.log(this.$refs.child.$refs.root) // 子のdiv要素にアクセス
}
}
</script>
解答17
<template functional>
<button
class="functional-btn"
v-bind="attrs"
v-on="listeners"
>
<slot></slot>
</button>
</template>
<style>
.functional-btn {
padding: 8px 16px;
background-color: #eee;
}
</style>
解答18
<template>
<div>Lifecycle Hooks Example</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted')
},
updated() {
console.log('Component updated')
}
}
</script>
上級解答
解答19
親コンポーネント:
<template>
<DynamicSlotComponent>
<template v-slot:[slotName]>
Dynamic Slot Content
</template>
</DynamicSlotComponent>
</template>
<script>
import DynamicSlotComponent from './DynamicSlotComponent.vue'
export default {
components: { DynamicSlotComponent },
data() {
return {
slotName: 'header' // 動的に変更可能
}
}
}
</script>
子コンポーネント:
<template>
<div>
<slot name="header"></slot>
</div>
</template>
解答20
<script>
export default {
render() {
return this.$h('div', [
this.$h('h1', 'Title'),
this.$h('p', 'Content')
])
}
}
</script>
解答21
<template>
<div v-highlight>Click me to highlight</div>
</template>
<script>
export default {
directives: {
highlight: {
mounted(el) {
el.addEventListener('click', () => {
el.style.backgroundColor = 'yellow'
})
}
}
}
}
</script>
解答22
// GlobalComponent.vue
<template>
<div>Global Component</div>
</template>
<script>
export default {
name: 'GlobalComponent'
}
</script>
// プラグインインストール用ファイル
export default {
install(app) {
app.component('GlobalComponent', require('./GlobalComponent.vue').default)
}
}
解答23
子コンポーネント:
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item"></slot>
</li>
</ul>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true
}
}
}
</script>
親コンポーネント:
<template>
<ItemList :items="items">
<template v-slot:default="slotProps">
{{ slotProps.item.name }} - {{ slotProps.item.price }}円
</template>
</ItemList>
</template>
<script>
import ItemList from './ItemList.vue'
export default {
components: { ItemList },
data() {
return {
items: [
{ id: 1, name: 'Item 1', price: 1000 },
{ id: 2, name: 'Item 2', price: 2000 }
]
}
}
}
</script>
解答24
<template>
<teleport to="body">
<div class="modal" v-if="show">
<div class="modal-content">
<slot></slot>
<button @click="show = false">Close</button>
</div>
</div>
</teleport>
</template>
<script>
export default {
data() {
return {
show: false
}
},
methods: {
open() {
this.show = true
},
close() {
this.show = false
}
}
}
</script>
<style>
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 5px;
}
</style>