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

Commit

Permalink
feat: use defineSlots (#262)
Browse files Browse the repository at this point in the history
  • Loading branch information
ubugeeei authored Apr 21, 2024
1 parent e5c0f30 commit ef1e1c0
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 74 deletions.
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

0 comments on commit ef1e1c0

Please sign in to comment.