首页 > 编程笔记
Vue slot(插槽)详解
在 Vue 中,组件是当作自定义的 HTML 元素来使用的,其元素可以包括属性和内容,通过组件定义的 prop 来接收属性值,那么组件的内容怎么实现呢?可以使用插槽(slot 元素)来解决。
在 Vue 实例中使用这个组件:
在 Chrome 浏览器中运行程序,渲染的结果如下图所示。
图 1 插槽的基本用法
如果 page 组件中没有 slot 元素,<page> 元素中的内容将不会渲染到页面。
有一条规则要记住:父组件模板中的所有内容都是在父级作用域中编译的,子组件模板中的所有内容都是在子作用域中编译的。
图 2 设置插槽的默认内容
在向命名插槽提供内容的时候,可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
然而,如果希望更明确一些,仍然可以在一个 <template> 中包裹默认命名插槽的内容:
图 3 命名插槽
与 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容(v-slot:)替换为字符 #。例如下面的代码:
在父级作用域下使用该组件时,可以给 v-slot 指令一个值来定义组件提供的插槽 prop 的名字。代码如下:
【实例】访问插槽的内容。
图 4 命名插槽
图 5 解构插槽prop
插槽的基本用法
下面定义一个组件:vm.component('page', { template:'<div><slot></slot></div>' });在 page 组件中,div 元素内容定义了 slot 元素,可以把它理解为占位符。
在 Vue 实例中使用这个组件:
<div id="app"> <page>如今直上银河去,同到牵牛织女家。</page> </div>page 元素给出了内容,在渲染组件时,这个内容会置换组件内部的 <slot> 元素。
在 Chrome 浏览器中运行程序,渲染的结果如下图所示。
图 1 插槽的基本用法
如果 page 组件中没有 slot 元素,<page> 元素中的内容将不会渲染到页面。
编译作用域
当想通过插槽向组件传递动态数据时,例如:<page>欢迎来到{{name}}的官网</page>代码中,name 属性是在父组件作用域下解析的,而不是 page 组件的作用域。在 page 组件中定义的属性,在父组件中是访问不到的,这就是编译作用域。
有一条规则要记住:父组件模板中的所有内容都是在父级作用域中编译的,子组件模板中的所有内容都是在子作用域中编译的。
默认内容
有时为一个插槽设置默认内容是很有用的,它只会在没有提供内容的时候被渲染。例如在一个 <submit-button> 组件中:<button type="submit"> <slot></slot> </button>如果希望这个 <button> 内绝大多数情况下都渲染文本“Submit”,可以将“Submit”作为默认内容放在 <slot> 标签内:
<button type="submit"> <slot>Submit</slot> </button>现在在一个父组件中使用 <submit-button>,并且不提供任何插槽内容:
<submit-button></submit-button>默认内容“Submit”将会被渲染:
<button type="submit"> Submit </button>但是如果提供内容:
<submit-button> 提交 </submit-button>则这个提供的内容将会替换掉默认值 Submit,渲染如下:
<button type="submit"> 提交 </button>【实例】设置插槽的默认内容。
<div id="app"> <page>流年莫虚掷,华发不相容。</page> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ }); vm.component('page', { template:`<button type="submit"> <slot>Submit</slot> </button> ' }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,渲染的结果如下图所示。
图 2 设置插槽的默认内容
命名插槽
在组件开发中,有时需要使用多个插槽。例如对于一个带有如下模板的 <page-layout> 组件:<div class="container"> <header> <!-- 我们希望把页头放这里 --> </header> <main> <!-- 我们希望把主要内容放这里 --> </main> <footer> <!-- 我们希望把页脚放这里 --> </footer> </div>对于这样的情况,<slot> 元素有一个特殊的特性 name,它用来命名插槽。因此,可以定义多个名字不同的插槽,例如下面的代码:
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>一个不带 name 的 <slot> 元素,它有默认的名字“default”。
在向命名插槽提供内容的时候,可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
<page-layout> <template v-slot:header> <h1>这里有一个页面标题</h1> </template> <p>这里有一段主要内容</p> <p>和另一段主要内容</p> <template v-slot:footer> <p>这是一些联系方式</p> </template> </page-layout>现在 <template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
然而,如果希望更明确一些,仍然可以在一个 <template> 中包裹默认命名插槽的内容:
<page-layout> <template v-slot:header> <h1>这里有一个页面标题</h1> </template> <template v-slot:default> <p>这里有一段主要内容</p> <p>和另一段主要内容</p> </template> <template v-slot:footer> <<p>这是一些联系方式</p> </template> </page-layout>上面两种写法都会渲染出如下代码:
<div class="container"> <header> <h3>这里有一个页面标题</h3> </header> <main> <p>这里有一段主要内容</p> <p>和另一段主要内容</p> </main> <footer> <p>这是一些联系方式</p> </footer> </div>【实例】命名插槽。
<div id="app"> <page-layout> <template v-slot:header> <h2 align='center'>书河上亭壁</h2> </template> <template v-slot:main> <h3>岸阔樯稀波渺茫,独凭危槛思何长。</h3> <h3>萧萧远树疏林外,一半秋山带夕阳。</h3> </template> <template v-slot:footer> <p align='right'>经典古诗</p> </template> </page-layout> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ }); vm.component('page-layout', { template:` <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot name="main"></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> ` }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,效果如下图所示。
图 3 命名插槽
与 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容(v-slot:)替换为字符 #。例如下面的代码:
<page-layout> <template #header> <h1>这里有一个页面标题</h1> </template> <template #main> <p>这里有一段主要内容</p> <p>和另一段主要内容</p> </template> <template #footer> <<p>这是一些联系方式</p> </template> </page-layout>
作用域插槽
在父级作用域下,在插槽的内容中是无法访问子组件的数据属性的,但有时需要在父级的插槽内容中访问子组件的属性,我们可以在子组件的 <slot> 元素上使用 v-bind 指令绑定一个 prop 属性。看下面的组件代码:vm.component('page-layout', { data:function(){ return{ info:{ name:'小明', age:18, sex:"男" } } }, template:` <button> <slot v-bind:values="info"> {{info.name}} </slot> </button> ` });这个按钮可以显示 info 对象中的任意一个,为了让父组件可以访问 info 对象,在 <slot> 元素上使用 v-bind 指令绑定一个 values 属性,称为插槽 prop,这个 prop 不需要在 props 选项中声明。
在父级作用域下使用该组件时,可以给 v-slot 指令一个值来定义组件提供的插槽 prop 的名字。代码如下:
<page-layout> <template v-slot:default="slotProps"> {{slotProps.values.name}} </template> </page-layout>因为 <page-layout> 组件内的插槽是默认插槽,所以这里使用其默认的名字 default,然后给出一个名字 slotProps,这个名字可以随便取,代表的是包含组件内所有插槽 prop 的一个对象,然后就可以在父组件中利用这个对象访问子组件的插槽 prop,prop 是绑定到 info 数据属性上的,所以可以进一步访问 info 的内容。示例代码如下。
【实例】访问插槽的内容。
<div id="app"> <page-layout> <template v-slot:default="slotProps"> {{slotProps.values.city}} </template> </page-layout> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ }); vm.component('page-layout', { data:function(){ return{ info:{ name:'苹果', price:8.86, city:"深圳" } } }, template:` <button> <slot v-bind:values="info"> {{info.city}} </slot> </button> ` }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,效果如下图所示。
图 4 命名插槽
解构插槽prop
作用域插槽的内部工作原理是将插槽内容传入函数的单个参数中:function (slotProps) { // 插槽内容 }这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下(单文件组件或现代浏览器),也可以使用 ES 6 解构来传入具体的插槽 prop,示例代码如下:
<current-verse v-slot="{ verse }"> {{ verse.firstContent }} </current-user>这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其他可能,例如将 verse 重命名为 poetry:
<current-verse v-slot="{ verse: poetry }"> {{ poetry.firstContent }} </current-verse>甚至可以定义默认的内容,用于插槽 prop 是 undefined 的情形:
<current-verse v-slot="{ verser = { firstContent: '古诗' } }"> {{ verse.Content}} </current-verser>【实例】解构插槽prop。
<div id="app"> <current-verse> <template v-slot="{verse:poetry}"> {{poetry.firstContent }} </template> </current-verse> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ }); vm.component('currentVerse', { template: ' <span><slot :verse="verse">{{ verse.lastContent }}</slot></span>', data:function(){ return { verse: { firstContent: '此心随去马,迢递过千峰。', secondContent: '野渡波摇月,空城雨翳钟。' } } } }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,效果如下图所示。
图 5 解构插槽prop