Vue中嵌套slot的使用

源于工作中一次组件的重构,这里重新写一个例子来练习下嵌套 slot 的使用。

嵌套 slot 需要使用到 v-slot 命令,使用该命令需要确保Vue 版本为 2.6.0 +。

完整的代码可见:TodoExamples.

示例

问题描述

现有一个 ToDoList 组件,其子组件 ToDoItem, 组件内容分别如下:

<!-- ToDoItem.vue -->

<template>
  <li>
    <span class="text-muted mr-1">{{ item.id }}.</span> {{ item.title }}
  </li>
</template>

<script>
export default {
  props: [ 'item' ]
}
</script>

<!-- ToDoList.vue -->

<template>
  <ul>
    <ToDoItem v-for="item in items" :key="item.id" :item="item" />
  </ul>
</template>

<script>
import ToDoItem from './ToDoItem'

export default {
  components: { ToDoItem },
  props: [ 'items' ]
}
</script>

在 schedule 中调用 ToDoList 组件:

<!-- schedule.vue -->

<template>
  <div class="container">
    <div class="row">
      <div class="col-md-6 offset-md-3">
        <ToDoList :items="items" />
      </div>
    </div>
  </div>
</template>

<script>
import ToDoList from '~/components/ToDoList'
import _ from 'lodash'

export default {
  components: { ToDoList },
  data() {
    return {
      items: [
        { id: 1, title: 'todo item 1' },
        { id: 2, title: 'todo item 2' },
        { id: 3, title: 'todo item 3' },
        { id: 4, title: 'todo item 4' }
      ],
      finished: [ 4, 2 ]
    }
  }
}
</script>

页面效果如下:

在 schedule 中,有一个数组 finished,里面存放了已经完成的 item 的 ID,现在需要在已经完成的 item 后打上☑️。

实现如下效果:

解决方法

要实现上述效果,有多个方法,这里采用嵌套 slot 的方式来解决。

主要思路:通过在 slot 上绑定属性, 让 schedule 层可以访问到 ToDoItem 中的 item,然后通过判断 item 是否在 finished 中出现,决定是否将☑️插入到组件 ToDoItem 中。

具体操作如下:

  • 给ToDoItem 添加具名插槽 after,同时将组件中的 item 作为 slot 的一个特性绑定上去:

    【将 item 绑定在 slot 上,是为了让父级组件 ToDoList 中的插槽可以访问到该组件中的 item】

    <!-- ToDoItem.vue -->
    
    <template>
      <li>
        <span class="text-muted mr-1">{{ item.id }}.</span> {{ item.title }}
        <slot name="after" :item="item" />
      </li>
    </template>
    
    <script>
    export default {
      props: [ 'item' ]
    }
    </script>
    
  • 在 ToDoList 组件中,引用 ToDoItem 的地方,嵌套 slot,修改 ToDoList.vue 如下:

    <!-- ToDoList.vue -->
    
    <template>
      <ul>
        <ToDoItem v-for="item in items" :key="item.id" :item="item">
          <template v-slot:after="{ item }">
            <slot name="todo-item-after" :item="item" />
          </template>
        </ToDoItem>
      </ul>
    </template>
    
    <script>
    import ToDoItem from './ToDoItem'
    
    export default {
      components: { ToDoItem },
      props: [ 'items' ]
    }
    </script>
    

    这里给 ToDoList 添加了一个具名插槽:todo-item-after,同时将 子组件 ToDoItem 中的具名插槽 after 所绑定的特性 item 绑定到 todo-item-after 上,这样外层的 schedule 就可以访问到 ToDoItem 中的 item了。

  • 修改 schedule.vue,添加函数 isFinished 用于判断是否需要显示☑️:

    <!-- schedule.vue -->
    
    <template>
      <div class="container">
        <div class="row">
          <div class="col-md-6 offset-md-3">
            <ToDoList :items="items">
              <template v-slot:todo-item-after="{ item }">
                <i v-if="isFinished(item)" class="fas fa-check" />
              </template>
            </ToDoList>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    import ToDoList from '~/components/ToDoList'
    import _ from 'lodash'
    
    export default {
      components: { ToDoList },
      data() {
        return {
          items: [
            { id: 1, title: 'todo item 1' },
            { id: 2, title: 'todo item 2' },
            { id: 3, title: 'todo item 3' },
            { id: 4, title: 'todo item 4' }
          ],
          finished: [ 4, 2 ]
        }
      },
      methods: {
        isFinished(item) {
          return _.includes(this.finished, item.id)
        }
      }
    }
    </script>
    

    如此,便完成了。

参考

插槽