Skip to content
This repository was archived by the owner on May 1, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions content/1.vue/5.components/.template/files/ChildComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup lang="ts">
defineProps<{
message: string
name: string
}>()

const emit = defineEmits<{
'update:name': [string]
}>()
</script>

<template>
<div class="child-component">
<h2>Child Component</h2>
<p>{{ message }}</p>
<input
type="text"
:value="name"
@input="emit('update:name', $event.target.value)"
>
</div>
</template>

<style scoped>
.child-component {
border: solid red;
padding: 1rem;
}
</style>
21 changes: 21 additions & 0 deletions content/1.vue/5.components/.template/files/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'

const name = ref('John Doe')

function updateName(value: string) {
name.value = value
}
</script>

<template>
<div>
<h1>Parent Component</h1>
<p>Hi, {{ name }} 👋</p>
<ChildComponent
message="Hello from Parent!"
:name="name"
@update:name="updateName"
/>
</div>
</template>
2 changes: 1 addition & 1 deletion content/1.vue/5.components/.template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const meta: GuideMeta = {
startingFile: 'app.vue',
features: {
terminal: false,
fileTree: false,
fileTree: true,
navigation: false,
},
}
25 changes: 25 additions & 0 deletions content/1.vue/5.components/.template/solutions/ChildComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script setup lang="ts">
defineProps<{
message: string
}>()

const name = defineModel<string>()
</script>

<template>
<div class="child-component">
<h2>Child Component</h2>
<p>{{ message }}</p>
<input
v-model="name"
type="text"
>
</div>
</template>

<style scoped>
.child-component {
border: solid red;
padding: 1rem;
}
</style>
16 changes: 16 additions & 0 deletions content/1.vue/5.components/.template/solutions/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'

const name = ref('John Doe')
</script>

<template>
<div>
<h1>Parent Component</h1>
<p>Hi, {{ name }} 👋</p>
<ChildComponent
v-model="name"
message="Hello from Parent!"
/>
</div>
</template>
118 changes: 117 additions & 1 deletion content/1.vue/5.components/index.md
Original file line number Diff line number Diff line change
@@ -1 +1,117 @@
# Components
---
ogImage: true
---

# コンポーネント

Vue.js のコンポーネントは、UI を小さな再利用可能な部分に分割するための基本的な単位です。\
特に Single File Components (SFC) を使うことで、HTML、CSS、および JavaScript を 1 つの `.vue` ファイルにまとめることができます。

## 基本的な SFC の構造

SFC は基本的に以下のような `<script>`, `<template>`, `<style>` の 3 つのセクションで構成されます。

```vue
<script setup lang="ts">
import { ref } from 'vue'

const message = ref('Hello, Vue!')

function sendMessage() {
console.log(message.value)
}
</script>

<template>
<p>{{ message }}</p>
<button type="button" @click="sendMessage">
Click me
</button>
</template>

<style scoped>
p {
color: red;
}
</style>
```

この例では、`<script>`, `<template>`, `<style>` の 3 つのセクションが使われています。

- `<script setup>`: コンポーネントのロジック部分を定義します。`<script setup>` を使用することで、Composition API を簡潔に書くことができます。
- `<template>`: コンポーネントのビュー部分を定義します。
- `<style scoped>`: コンポーネント固有のスタイルを定義します。`scoped` 属性を追加することで、このコンポーネントのスタイルが他のコンポーネントに影響を与えないようにします。

## コンポーネントの再利用

`.vue` ファイルで定義した SFC は、以下のように `<script setup>` でインポートすることでテンプレート内で再利用することができます。

```vue
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'
</script>

<template>
<ChildComponent />
</template>
```

## コンポーネント間のデータの受け渡し

Vue コンポーネント間でデータをやり取りする基本的な方法として、`props` と `emit` を使用します。

- `props`: 親コンポーネントから子コンポーネントにデータを渡すための方法です。
- `emit`: 子コンポーネントから親コンポーネントにイベントを発火するための方法です。

それぞれ `defineProps`, `defineEmits` で登録します。\
使い方は右側のプレイグラウンド、または [API ドキュメント](https://ja.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)から確認できます。

## 双方向バインディング

コンポーネント上で `v-model` を使うことで双方向バインディングを実装できます。\
以下の例では、コンポーネント中で宣言された `value` が `<input>` の値とバインドされ、`<input>` の入力値が `value` に反映されます。

```vue
<script setup lang="ts">
const value = ref('')
</script>

<template>
<input v-model="value" type="text">
</template>
```

また、SFC で `defineModel` を使うことで、親コンポーネントから `v-model` 経由で使用できる双方向バインディングの `props` を宣言できます。

```vue
<!-- Child.vue -->
<script setup lang="ts">
const localValue = defineModel<string>()
</script>

<template>
<input v-model="localValue" type="text">
</template>
```

```vue
<!-- Parent.vue -->
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'

const parentValue = ref('Initial Value')
</script>

<template>
<ChildComponent v-model="parentValue" />
</template>
```

## チャレンジ

プレイグラウンドの `ChildComponent.vue` は `props` と `emit` を使って双方向バインディングを実現しています。\
これを `defineModel` を使って簡潔に書き直してみましょう。

もし手詰まりになったら、解決策を確認するためのボタンをクリックして、ヒントを得ることができます。

:ButtonShowSolution{.bg-faded.px4.py2.rounded.border.border-base.hover:bg-active.hover:text-primary.hover:border-primary:50}