Posted at

[Vue.js]非推奨になったslot-scopeの代わりにv-slotを使う


slot-scope

子コンポーネントから親コンポーネントにプロパティを渡すための構文。

ScopedSlotsとこれを用いることでデータの振る舞いと見た目を分離することができる。


TODOアプリ

簡単なTODOアプリのコードを例に使い方を見ていきます。

qiita-vue-gif.gif


Components

Parent Component


  • App.vue   ← 全てのTodoを描画する

Child Component


  • Todo.vue  ← Todoの見た目を定義

  • TodosData.vue  ← TodoDataの振る舞いを定義


slot-scopeとv-slot

Version 2.5以前はslot-scope

Version 2.6以降はslot-scopeとv-slot

が用意されている。

Vue 3.xからはslot-scopeはなくなるかもしれないので注意。


共通(Todo.vue, TodosData.vue)


Todo.vue

<script>

export default {
props: {
todo: String,
required: true,
default: ''
},
methods: {
handleTodoDone () {
this.$emit('todoDone')
}
}
}
</script>

<template>
<div>
<span>
{{ todo }}
</span>
<button @click="handleTodoDone">
Done
</button>
</div>
</template>



TodosData.vue

<script>

export default {
data () {
return {
todos: [],
newTodoText: ''
}
},
methods: {
handleTodoDone (time) {
for (let i = 0; i < this.todos.length; i++) {
if (this.todos[i].time === time) this.todos.splice(i, 1)
}
},
handleTodoAdd () {
if (this.newTodoText === '') return
const time = this.getTime()
const newTodo = { text: this.newTodoText, time: time }
this.todos.push(newTodo)
this.newTodoText = ''
},
handleNewTodoChange (newTodo) {
this.newTodoText = newTodo
},
getTime () {
const now = new Date()
const hour = String(now.getHours())
const minute = String(now.getMinutes())
const second = String(now.getSeconds())
const time = hour + minute + second
return time
}
},
render (createElement) {
return this.$scopedSlots.default({
todos: this.todos,
todoDone: this.handleTodoDone,
todoAdd: this.handleTodoAdd,
newTodoText: this.newTodoText,
newTodoChange: this.handleNewTodoChange
})[0]
}
}
</script>

scopedSlotsを使うことで無駄なタグを生成せずに済む。


Vue 2.5以前


App.vue

<script>

export default {
components: { Todo, TodosData }
}
</script>

<template>
<div>
<todos-data>
<template slot-scope="todosData">
<div>
<div>
<input
:value="todosData.newTodoText"
type="text"
@change="(e) => todosData.newTodoChange(e.target.value)"
>
<button @click="todosData.todoAdd">
ADD
</button>
</div>
<template v-for="todo in todosData.todos">
<todo
:todo="todo.text"
:key="todo.time"
@todoDone="todosData.todoDone(todo.time)"
/>
</template>
</div>
</template>
</todos-data>
</div>
</template>


slot-scope 構文を用いてスロットプロパティを受け渡すには、親コンポーネントは <template> に対して slot-scope 属性を使う必要があります。


Vue 2.6以降


App.vue

<script>

export default {
components: { Todo, TodosData }
}
</script>

<template>
<div>
<todos-data v-slot="todosData">
<div>
<div>
<input
:value="todosData.newTodoText"
type="text"
@change="(e) => todosData.newTodoChange(e.target.value)"
>
<button @click="todosData.todoAdd">
ADD
</button>
</div>
<template v-for="todo in todosData.todos">
<todo
:todo="todo.text"
:key="todo.time"
@todoDone="todosData.todoDone(todo.time)"
/>
</template>
</div>
</todos-data>
</div>
</template>


v-slot 構文では、デフォルトスロットだけの場合はコンポーネントのタグをスロットのテンプレートとして使うことができます。

つまり、上記のように TodosData に記述することができます。


え、一緒じゃん?

はい、書く位置が変わっただけでやっていることは変わっていません。

そもそもdeprecatedになった理由が、

「コンポーネントがテンプレート内でどのプロパティを提供しているのかパッと見分かりにくい」

というものだったので当然と言えば当然ですが。


終わりに

意外とまだslot-scopeを見かけるのでまとめてみました。

Vue.js楽しい!


補足


  1. CSSは省略しています(というか色々端折ってます)

  2. Vue公式

  3. RFCs