首页 > 编程笔记

Vue slot插槽完全攻略

项目中有很多组件显示的内容,在不同的地方使用的时候,有些内容需要变化,这时候可以用插槽内容分发功能实现。

例如,开发人员可以定义一个组件,专门用来显示提示信息。只是显示的信息包含在插槽 slot 元素中,满足在不同的地方,提示的内容不一样。

MessageAlert 组件中包含了 slot 插槽,运行的时候,就会将组件元素包含的内容动态地渲染到视图中,代码如下:
...
<div id="app"> 
    <message-alert>普通</message-alert> 
    <message-alert>警告</message-alert> 
    <message-alert>错误</message-alert> 
</div> 
 
<script> 
// 定义MessageAlert组件 
const MessageAlert = { 
    template: ` 
        <div> 
            <strong>消息:</strong> 
            <slot></slot> 
        </div> 
    ` 
}; 
 
// 注册全局MessageAlert组件 
Vue.component('message-alert', MessageAlert); 
 
// 创建Vue实例并挂载到#app元素 
const vm = new Vue({ 
    el: '#app' 
}); 
</script> 
...

插槽的缺省内容和编译作用域

在实战中,slot 中往往希望有缺省值,实现起来很简单,直接在子元素的 slot 之间包含缺省值即可,调用的时候,如果有新值,则自动会将缺省值覆盖,如 slot 缺省值 /slot。

当然,分发到 slot 中的内容,可以包含动态内容,此时动态的变量只能使用父组件里面的 property,代码如下:
... 
<div id="app"> 
    <!-- 使用缺省值 --> 
    <slot-dft-content></slot-dft-content> 
     
    <!-- 使用静态内容 --> 
    <slot-dft-content>提交</slot-dft-content> 
     
    <!-- 编译作用域 --> 
    <!-- parentValue是在父组件vm的data中定义的 --> 
    <slot-dft-content>{{parentValue}}</slot-dft-content> 
     
    <!-- 如果放开下面这行,会报异常,因为sonValue是在子组件SlotDftContent的data中定义的 --> 
    <!-- <slot-dft-content>{{sonValue}}</slot-dft-content> --> 
</div> 
 
<script> 
// 注册SlotDftContent组件 
Vue.component('slot-dft-content', { 
    template: ` 
        <button type="submit"> 
            <slot>提交</slot> 
        </button> 
    `, 
    data: function() { 
        return { 
            sonValue: 'sonContent' 
        } 
    } 
}); 
 
// 创建Vue实例并挂载到#app元素 
const vm = new Vue({ 
    el: '#app', 
    data: { 
        parentValue: 'parentContent' 
    } 
}); 
</script> 
...

具名插槽

有时候在一个子元素中需要定义多个插槽,使用子元素的时候,将不同内容分发到不同插槽中去。这时候,开发人员可以使用 slot 的 name 属性,给每个 slot 命名(没有命名的为 default),使用子组件时,用 template 元素包含要分发到每个 slot 的内容,使用“v-slot:目标插槽的名称”来指定对应的分发插槽。

示例代码如下:
... 
<div id="app"> 
    <base-layout> 
        <!-- 具名插槽:header --> 
        <template v-slot:header>头</template> 
         
        <!-- 具名插槽:footer --> 
        <template v-slot:footer>尾</template> 
         
        <!-- 默认插槽内容 --> 
        <p>默认内容</p> 
    </base-layout> 
</div> 
 
<script> 
// 注册BaseLayout组件 
Vue.component('base-layout', { 
    template: ` 
        <div class="container"> 
            <header> 
                <!-- header具名插槽 --> 
                <slot name="header"></slot> 
            </header> 
            <main> 
                <!-- 默认插槽 --> 
                <slot></slot> 
            </main> 
            <footer> 
                <!-- footer具名插槽,当前为空 --> 
                <slot name="footer"></slot> 
            </footer> 
        </div> 
    ` 
}); 
 
// 创建Vue实例并挂载到#app元素 
const vm = new Vue({ 
    el: '#app' 
}); 
</script> 
...

作用域插槽

有时候,需要获取子元素的数据,进行整理后再分发给插槽,这时候可以使用作用域插槽。

开发人员可以在 slot 元素中,使用 v-bind 指令将对象绑定后,传递到组件外面,如:slot v-bind:userAttr="user".../slot 就是将子元素中的 user 数据,通过 v-bind 指令绑定到 userAttr 属性上。

userAttr 属性又称为 slot property,在父组件中,可以在 template 元素中使用指定的插槽属性变量操作 userAttr 绑定的 user 数据,代码如下:
<div id="app"> 
    <!-- 使用作用域插槽 --> 
    <range-slot> 
        <template v-slot:default="slotProps"> 
            {{ slotProps.userAttr.userName }}整理好了 
        </template> 
    </range-slot> 
</div> 
 
<script> 
// 定义RangeSlot组件 
Vue.component('range-slot', { 
    template: ` 
        <div> 
            <!-- 作用域插槽,向父组件传递userAttr属性 --> 
            <slot v-bind:userAttr="user"></slot> 
        </div> 
    `, 
    data: function() { 
        return { 
            user: { 
                name: '李四', 
                userName: 'lisi' 
            } 
        } 
    } 
}); 
 
// 创建Vue实例并挂载到#app元素 
const vm = new Vue({ 
    el: '#app' 
}); 
</script> 
...

动态插槽名

动态指令参数也可以用在 v-slot 上,用来定义动态的插槽名。特别要注意的是,这里不支持驼峰命名的变量名,语法如下:
<range-slot> 
    <template v-slot:[动态的插槽名称]> 
        ...
    </template> 
</range-slot> 

具名插槽的缩写

跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容(v-slot:)替换为字符 #。

v-slot:header 可以被重写为 # header,代码如下:
<base-layout> 
    <!-- Header template --> 
    <template #header> 
      <h1>Here might be a page title</h1> 
    </template> 
 
    <!-- Main content --> 
    <p>A paragraph for the main content.</p> 
    <p>And another one.</p> 
 
    <!-- Footer template --> 
    <template #footer> 
      <p>Here's some contact info</p> 
    </template>  
</base-layout> 
然而,和其他指令一样,该缩写只在有参数的时候才可用,这意味着以下语法是无效的:
<!-- 这样会触发一个警告 -->
<current-user #="{ user }"> 
    {{ user.firstName }} 
</current-user> 
如果开发人员希望使用缩写,则必须始终以明确的插槽名取而代之,代码如下:
<current-user #default="{ user }"> 
    {{ user.firstName }} 
</current-user>

推荐阅读