slot-scope
子コンポーネントから親コンポーネントにプロパティを渡すための構文。
ScopedSlotsとこれを用いることでデータの振る舞いと見た目を分離することができる。
TODOアプリ
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)
<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>
<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以前
<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以降
<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楽しい!