Skip to content
This repository was archived by the owner on May 25, 2024. It is now read-only.

feat: use defineSlots #262

Merged
merged 1 commit into from
Apr 21, 2024
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
1 change: 1 addition & 0 deletions packages/app/src/components/Card.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
defineProps<{ name: string, price: number, image: string }>()
defineSlots<{ body: () => any }>()

/** 価格を3桁ごとのカンマ付きで返す */
function pricePrefix(price: number): string {
Expand Down
150 changes: 76 additions & 74 deletions packages/docs/src/slot.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# コンポーネントにスロットを使用する

商品をコンポーネント化したことで、`Card` コンポーネントに必要な情報を `props` で渡すだけとなり、コードが見やすくなりました
商品をコンポーネント化したことで、`Card` コンポーネントに必要な情報を `props` で渡すだけとなり、コードが見やすくなり、再利用性も向上しました

```vue
<template>
Expand Down Expand Up @@ -35,13 +35,15 @@

### スロットとは

Vue.js のスロットでは、親コンポーネントから子コンポーネントにコンテンツを渡してレンダリングすることが可能です。スロットを使用すると、コンポーネントの `props` の修正に手を入れることなく、表示するコンテンツを変更できるため、コンポーネントの再利用性と柔軟性が高まります。スロットには、**スロットコンテンツ**と**スロットアウトレット**という仕組みがあるので、説明していきます。
Vue.js のスロットでは、親コンポーネントから子コンポーネントにコンテンツを渡してレンダリングすることが可能です。\
スロットを使用するとことによって親コンポーネントからレンダリングしたいコンテンツを挿入できるため、コンポーネントの再利用性と柔軟性が高まります。

#### スロットコンテンツ

スロットコンテンツとは、子コンポーネントへ渡すコンテンツのことを指します。

コンテンツを渡す方法は、親コンポーネントで子コンポーネントを呼び出し、子コンポーネントの要素へレンダリングしたいコンテンツを定義します。スロットコンテンツとして、プレーンテキスト、HTML 要素、他のコンポーネントなど、さまざまな種類を渡すことができます。
コンテンツを渡す方法は、親コンポーネントで子コンポーネントを呼び出し、子コンポーネントの要素へレンダリングしたいコンテンツを定義します。\
スロットコンテンツとして、プレーンテキスト、HTML 要素、他のコンポーネントなど、さまざまな種類を渡すことができます。

#### 親コンポーネント

Expand All @@ -54,21 +56,25 @@ Vue.js のスロットでは、親コンポーネントから子コンポーネ
#### スロットアウトレット

親コンポーネントでスロットコンテンツを定義しましたが、子コンポーネント側では、スロットコンテンツを受け取るためのスロットアウトレットを用意する必要があります。

コンテンツを受け取る方法は、スロットコンテンツをレンダリングしたい場所に `slot` 要素を定義します。
コンテンツを受け取る方法は、スロットコンテンツをレンダリングしたい場所に `slot` 要素を定義します。\
また、`defineSlots` マクロを利用することで型定義を行うことができます。(親コンポーネント側でエディタの支援や型チェックを行えるようになります。)

#### 子コンポーネント

```vue
<div>
<!-- 親要素のスロットコンテンツがslot要素へレンダリングされる -->
<script setup lang="ts">
defineSlots<{ default: () => any }>() // default については後述
</script>

<template>
<!-- 親要素のスロットコンテンツが slot 要素へレンダリングされる -->
<slot />
</div>
</template>
```

スロットコンテンツで紹介した親コンポーネントのコードと、スロットアウトレットの子コンポーネントを組み合わせると、最終的に表示されるコードは以下のようになります。

```vue
```html
<div>
<!-- スロットコンテンツがスロットアウトレットにレンダリングされている -->
スロットコンテンツ
Expand All @@ -84,6 +90,13 @@ Vue.js のスロットでは、親コンポーネントから子コンポーネ
#### 子コンポーネント

```vue
<script setup lang="ts">
defineSlots<{
contents: () => any
footer: () => any
}>()
</script>

<template>
<div>
<h2>Child Component</h2>
Expand All @@ -104,6 +117,7 @@ Vue.js のスロットでは、親コンポーネントから子コンポーネ
<template #contents>
<p>コンテンツ</p>
</template>

<template #footer>
<p>フッター</p>
</template>
Expand Down Expand Up @@ -149,15 +163,14 @@ Vue.js のスロットでは、親コンポーネントから子コンポーネ
```vue{17-25}
<template>
<header class="header">
<img
src="/images/logo.svg"
alt="">
<img src="/images/logo.svg" alt="">
<h1>Vue.js ハンズオン</h1>
</header>
<main class="main">
<template
v-for="item in items"
:key="item.id">
:key="item.id"
>
<div
v-if="!item.soldOut"
class="item"
Expand All @@ -167,7 +180,8 @@ Vue.js のスロットでは、親コンポーネントから子コンポーネ
:id="item.id"
:image="item.image"
:name="item.name"
:price="item.price">
:price="item.price"
>
<template #body>
<p>{{ item.description }}</p>
</template>
Expand All @@ -184,26 +198,8 @@ Vue.js のスロットでは、親コンポーネントから子コンポーネ

### スロットを利用し、テキストを挿入する

`Card` コンポーネントでは、`name` 属性に `body` を指定したスロットアウトレットを定義します。同時に、`props` から `description` を削除しておきます。

#### Card.vue / template

```vue{9}
<template>
<div class="thumbnail">
<img
:src="image"
alt="">
</div>
<div class="description">
<h2>{{ name }}</h2>
<slot name="body" />
<span>¥<span class="price">{{ pricePrefix(price) }}</span></span>
</div>
</template>
```

#### Card.vue / script
`Card` コンポーネントでは、`name` 属性に `body` を指定したスロットアウトレットを定義します。\
`props` からは `description` を削除しておきます。

```vue
<script setup lang="ts">
Expand All @@ -212,9 +208,24 @@ defineProps<{
image: string
name: string
price: number
description: string // [!code --]
}>()
// 省略

defineSlots<{ // [!code ++]
body: () => any // [!code ++]
}>() // [!code ++]
</script>

<template>
<div class="thumbnail">
<img :src="image" alt="">
</div>
<div class="description">
<h2>{{ name }}</h2>
<slot name="body" /> <!-- [!code ++] -->
<span>¥<span class="price">{{ pricePrefix(price) }}</span></span>
</div>
</template>
```

以上で、スロットの置き換えが完了です。見た目上の変化はありませんが、`body` のスロットコンテンツが表示されているかと思います。
Expand All @@ -231,7 +242,7 @@ defineProps<{

#### App.vue / script

```vue{15}
```vue
<script setup lang="ts">
import { ref } from 'vue'
import Card from './components/Card.vue'
Expand All @@ -246,9 +257,9 @@ const items = ref([
image: '/images/item1.jpg',
soldOut: false,
selected: false,
link: 'https://handson.vuejs-jp.org/'
link: 'https://handson.vuejs-jp.org/' // [!code ++]
},
//省略
// 省略
])
</script>
```
Expand All @@ -260,16 +271,17 @@ const items = ref([
```vue{10}
<template>
<!-- 省略-->
<Card
:id="item.id"
:image="item.image"
:name="item.name"
:price="item.price">
<template #body>
<p>{{ item.description }}</p>
<a v-if="item.link" :href="item.link">リンク</a>
</template>
</Card>
<Card
:id="item.id"
:image="item.image"
:name="item.name"
:price="item.price"
>
<template #body>
<p>{{ item.description }}</p>
<a v-if="item.link" :href="item.link">リンク</a> <!-- [!code ++] -->
</template>
</Card>
<!-- 省略-->
</template>
```
Expand All @@ -286,18 +298,7 @@ const items = ref([

```vue
<script setup lang="ts">
defineProps({
description: {
type: String,
default: '',
required: false
},
link: {
type: String,
default: '',
required: false
}
})
defineProps<{ description: string, link: string }>()
</script>

<template>
Expand All @@ -314,25 +315,26 @@ defineProps({
<script setup lang="ts">
import { ref } from 'vue'
import Card from './components/Card.vue'
import CardBody from './components/CardBody.vue';
import CardBody from './components/CardBody.vue'; // [!code ++]

//省略
// 省略
</script>

<template>
<!-- 省略-->
<Card
:id="item.id"
:image="item.image"
:name="item.name"
:description="item.description"
:price="item.price">
<template #body>
<CardBody
:description="item.description"
:link="item.link"/>
</template>
</Card>
<Card
:id="item.id"
:image="item.image"
:name="item.name"
:description="item.description"
:price="item.price"
>
<template #body>
<p>{{ item.description }}</p> <!-- [!code --] -->
<a v-if="item.link" :href="item.link">リンク</a> <!-- [!code --] -->
<CardBody :description="item.description" :link="item.link" /> <!-- [!code ++] -->
</template>
</Card>
<!-- 省略-->
</template>
```
Expand Down
Loading