赞
踩
目录
原来的 vue 插槽有三种 :匿名插槽、具名插槽 和 作用域插槽。不过,在 vue2.6+ 之后采用 v-slot 指令来代替原来的三种插槽。
v-slot 的特性:
插槽用 <slot></slot> 表示,它是一个:子组件提供给父组件的占位符,父组件可以在这个占位符中填充任何模板代码。
使用 <slot></slot> 标签创建一个插槽:
- <!-- 在 slot-component 组件中创建一个插槽-->
- <template>
- <slot></slot>
- </template>
在其他组件中使用已创建好的插槽:
- <template>
- <slot-component> hello world </slot-component>
- <!-- ... -->
- </template>
上述代码中,当 slot-component 组件渲染时,<slot></slot> 标签将会被替换为 “hello world”。不过,若 slot-component 组件的 template 中没有包含一个 <slot> 元素时,则该部分会被抛弃而不被渲染。
插槽的默认值只会在没有提供内容的时候才会被渲染。
例如:假设在 submit-button 组件中定义了一个插槽:
- <!-- submit-button 组件-->
- <button type="submit">
- <slot></slot>
- </button>
我们可能希望这个 <button> 内绝大多数情况下都渲染文本“Submit”。于是,可以通过给这个 <slot> 标签设置默认值来实现:
- <button type="submit">
- <slot>Submit</slot>
- </button>
然后在一个父级组件中使用 <submit-button> 组件:
- <!-- 提供内容 Save 时 -->
- <submit-button>Save</submit-button>
- <!-- 渲染为:<button type="submit"> Save </button> -->
-
- <!-- 不提供内容时 -->
- <submit-button></submit-button>
- <!-- 渲染为:<button type="submit"> Submit </button> -->
每个 <slot> 标签都会有一个默认的名字叫做:“default”。
当我们需要在一个组件中定义多个插槽时,必须通过 <slot> 标签的 name 属性给 <slot> 起一个独一无二的名字。
例如:
- <!-- base-layout 组件 -->
- <div class="container">
- <header>
- <slot name="header"></slot>
- </header>
- <main>
- <slot></slot>
- <!-- 等价于 <slot name="default"></slot> -->
- </main>
- <footer>
- <slot name="footer"></slot>
- </footer>
- </div>
在向具名插槽提供内容的时,可以在一个 <template> 元素上,通过使用 “v-slot:name” 指令(可以简写为 “#name”),将内容传递给指定名字的插槽。
- <base-layout>
- <template v-slot:header>
- <h1>欢迎你来到这个页面</h1>
- </template>
-
- <p>1234567890</p>
- <p>0987654321</p>
- <!-- 这等价于:
- <template v-slot:default>
- <p>1234567890</p>
- <p>0987654321</p>
- </template>
- -->
-
- <template #footer>
- <p>走吧走吧</p>
- </template>
- </base-layout>
现在 <template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为 “默认插槽的内容”。
最终渲染为:
- <div class="container">
- <header>
- <h1>欢迎你来到这个页面</h1>
- </header>
- <main>
- <p>1234567890</p>
- <p>0987654321</p>
- </main>
- <footer>
- <p>走吧走吧</p>
- </footer>
- </div>
【注意】
v-slot 只能添加在 <template> 上,只有一种情况除外:独占默认插槽(见下文)。
当我们在组件中需要给多个插槽分发内容时,就需要:给分发内容的插槽模版 指定与之对应的插槽名字。(不能使用 “默认插槽的缩写语法”,因为这是在给 “具名插槽” 分发内容。)
语法:
- v-slot:插槽的名字
- // 或者
- #插槽的名字
举个例子:
- <!-- current-user 组件 -->
- <template>
- <slot name="one"></slot>
- <slot name="two"></slot>
- <!-- 这是一个默认插槽(没起名字) -->
- <slot></slot>
- </template>
- <current-user>
- <template v-slot:one">
- <span>1</span>
- </template>
- <template #two>
- <span>2</span>
- </template>
- <!-- 默认插槽会启用默认的名字:default,可省略 -->
- <span>0</span>
- <!-- 等价于
- <template v-slot:default>
- <span>0</span>
- </template>
- -->
- </current-user>
这里涉及了 插槽 prop,请先阅读完下文的 “插槽的作用域”,然后再回看这部分内容。
独占默认插槽指的是:被提供的内容只有默认插槽,没有具名插槽。
当给独占默认插槽分发内容时,组件的标签才可以被当作插槽的模板来使用(省去了 <template> 标签)。
例如:
<current-user v-slot:default="slotProps"> hello world </current-user>
还可以简写为:
<current-user v-slot="slotProps"> hello world </current-user>
【注意】
“默认插槽的缩写语法” 不能和 “具名插槽” 混用,因为它会导致作用域不明确:
- <!-- 无效,会导致警告 -->
- <current-user v-slot="slotProps">
- {{ slotProps.user.firstName }}
- <template v-slot:other="otherSlotProps">
- slotProps is NOT available here
- </template>
- </current-user>
在使用插槽时,不在同一个组件中访问插槽的内容会产生插槽跨域。所以,插槽的作用域遵循一个原则:
举个反例:
- <!-- current-user 组件 -->
- <template>
- <slot>{{ user.lastName }}</slot>
- </template>
我们在使用 current-user 组件时,想换掉备用内容,用名而非姓来显示。如下:
- <template>
- <current-user> {{ user.firstName }} </current-user>
- </template>
上述代码不会正常工作,因为只有 current-user 组件可以访问到 user,而我们提供的内容是在 current-user 组件的父组件中渲染的,这就会产生 插槽跨域。
一般情况下,在父组件中使用子组件时,无法访问子组件中插槽里的内容。这就是插槽作用域的界限。那么能否打破这个界限呢?
通过使用插槽 prop 可以实现:在父组件中使用子组件时,能够访问到子组件中插槽里的内容。
插槽 prop——是一个包含了来自该 <slot> 元素上所有属性的对象。其中,这些属性是通过 v-bind 指令(可简写为 “:”)绑定在该 <slot> 元素上的。
语法:
- v-slot:插槽的名字='插槽 prop 的名字'
- // 或者
- #插槽的名字='插槽 prop 的名字'
这里请聚焦 “插槽 prop 的名字”。
例如:
为了让 user 在父级模板中可用,我们可以在 current-user 组件里,将 user 作为 <slot> 元素的一个 attribute 绑定上去:
- <!-- current-user 组件 -->
- <template>
- <slot v-bind:user="user"> {{ user.lastName }} </slot>
- </template>
在父级模板中使用 current-user 组件时,通过自定义 v-slot 的值来给该插槽的 prop 起名。这里给插槽的 prop 起名为 slotProps。那么,slotProps 对象包含了所有来自该插槽的属性:
- <current-user>
- <template v-slot:default="slotProps">
- {{ slotProps.user.firstName }}
- </template>
- </current-user>
其实这一步相当于给插槽 prop 起名。
插槽 prop 的更多应用示例:
- <!-- current-user 组件 -->
- <template>
- <slot name="one" :user="content"></slot>
- <slot name="two" :user="info"></slot>
- <!-- 这是一个默认插槽(没起名字) -->
- <slot :user="datas"></slot>
- </template>
- <current-user>
- <template v-slot:one="oneSlotProps">
- {{oneSlotProps.name}}
- </template>
- <template #two="twoSlotProps">
- {{twoSlotProps.age}}
- </template>
- <!-- 默认插槽会启用默认的名字:default -->
- <template v-slot:default="defaultSlotprops">
- {{defaultSlotprops.gender}}
- </template>
- </current-user>
方法与属性一样,都依赖于插槽 prop——在定义插槽的组件中将方法和属性抛出。在使用的组件中通过插槽 prop 接收后使用即可。
例如:
在子组件中产生:
- <template lang='pug'>
- .more-slot
- slot(name='addSlot' :add='addHandle')
- Button +
- slot(name='numSlot')
- span {{number}}
- </template>
- <script>
- export default {
- data () {
- number: 0
- },
- methods: {
- addHandle () {
- this.numer++
- }
- }
- }
- </script>
父组件中消费掉:
- <temolate lang='pug'>
- more-slot
- template(#addSlot='{add}')
- span(@click='add()') 增加
- template(#numSlot)
- </template>
- <script>
- import MoreSlot from './more-slot/index.vue'
-
- export default {
- components: {
- MoreSlot
- }
- }
- </script>
渲染为:
作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里:
- function (slotProps) {
- // 插槽内容
- }
这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。
在作用域插槽的内部工作原理的支持下,在单文件组件或现代浏览器中,你也可以使用 ES2015(ES6)的 “解构” 语法来传入具体的插槽 prop。
例如:
- <current-user v-slot="{ user, workInfo }">
- {{ user.firstName }}
- </current-user>
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。
例如,将 user 重命名为 person:
- <current-user v-slot="{ user: person }">
- {{ person.firstName }}
- </current-user>
解构插槽 Prop 时,给解构的属性设置默认值是有必要的,此默认值只会在当插槽 prop 是 undefined 的时候生效:
- <current-user v-slot="{ user = { firstName: 'Guest' } }">
- {{ user.firstName }}
- </current-user>
插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。
例如,我们要实现一个 <todo-list> 组件,它是一个列表且包含布局和过滤逻辑:
- <ul>
- <li
- v-for="todo in filteredTodos"
- v-bind:key="todo.id"
- >
- {{ todo.text }}
- </li>
- </ul>
我们可以将每个 todo 作为父级组件的插槽,以此通过父级组件对其进行控制,然后将 todo 作为一个插槽 prop 进行绑定:
- <ul>
- <li
- v-for="todo in filteredTodos"
- v-bind:key="todo.id"
- >
- <!--
- 我们为每个 todo 准备了一个插槽,
- 将 `todo` 对象作为一个插槽的 prop 传入。
- -->
- <slot name="todo" v-bind:todo="todo">
- <!-- 后备内容 -->
- {{ todo.text }}
- </slot>
- </li>
- </ul>
现在当我们使用 <todo-list> 组件的时候,我们可以选择为 todo 定义一个不一样的 <template> 作为替代方案,并且可以从子组件获取数据:
- <todo-list v-bind:todos="todos">
- <template v-slot:todo="{ todo }">
- <span v-if="todo.isComplete">✓</span>
- {{ todo.text }}
- </template>
- </todo-list>
这只是作用域插槽用武之地的冰山一角。想了解更多现实生活中的作用域插槽的用法,我们推荐浏览诸如 Vue Virtual Scroller、Vue Promised 和 Portal Vue 等库。
通过将插槽的名字指定为一个变量,来动态的加载不同的插槽。
例如:
- <base-layout>
- <template v-slot:[dynamicSlotName]>
- ...
- </template>
- </base-layout>
插槽的原理要从 Shadow DOM 谈起。
Shadow DOM 是普通的 DOM,但有两个区别:
您创建 DOM 节点并将它们附加为另一个元素的子元素。使用 shadow DOM,您可以创建一个附加到元素的作用域 DOM 树,但与它的实际子元素分开。这个范围子树称为影子树。它附加到的元素是它的影子主机。您在阴影中添加的任何内容都会成为托管元素的本地元素,包括<style>. 这就是 shadow DOM 实现 CSS 样式范围的方式。
- const header = document.createElement('header')
- const shadowRoot = header.attachShadow({mode: 'open'})
-
- shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'// 也可以使用 appendChild()。
Shadow DOM 使用<slot>元素将不同的 DOM 树组合在一起。 插槽是组件内的占位符,用户可以用自己的标记填充。通过定义一个或多个插槽,您可以邀请外部标记在组件的影子 DOM 中呈现。本质上,您是在说“在此处渲染用户的标记”。
Shadow DOM 在创建自定义元素时特别有用 。使用 shadow DOM 划分元素的 HTML、CSS 和 JS,从而生成“Web 组件”。
- // 使用自定义元素 API v1 注册新的 HTML 标签并定义其 JS 行为
- // 使用 ES6 类。 <fancy-tab> 的每个实例都将具有相同的原型。
- customElements.define('fancy-tabs', class extends HTMLElement {
- constructor() {
- super(); // 总是在构造函数中首先调用 super()。
-
- // 将影子根附加到 <fancy-tabs>。
- const shadowRoot = this.attachShadow({mode: 'open'});
- shadowRoot.innerHTML = `
- <style>#tabs { ... }</style> <!-- 样式的范围是 fancy-tabs! -->
- <div id="tabs">...</div>
- <div id="panels">...</div>
- `;
- }
- ...
- });
shadow DOM API 提供了 slotchange 事件——在插槽的分布式节点发生更改时触发。例如,如果用户在 light DOM 中添加/删除子项。
- const slot = this.shadowRoot.querySelector('#slot');
- slot.addEventListener('slotchange', e => {
- console.log('子节点改变了!');
- });
有时了解哪些元素与插槽相关联很有用。调用 slot.assignedNodes()以查找插槽正在渲染的元素。该 {flatten: true}选项还将返回插槽的后备内容(如果没有分发节点)。
例如,假设您的 shadow DOM 如下所示:
<slot><b>fallback content</b></slot>
用法 | 称呼 | 结果 |
---|---|---|
<my-component>组件文本</my-component> | slot.assignedNodes(); | [component text] |
<my-component></my-component> | slot.assignedNodes(); | [] |
<my-component></my-component> | slot.assignedNodes({flatten: true}); | [<b>fallback content</b>] |
element.assignedSlot告诉你你的元素被分配到哪个组件槽。
当一个事件从 shadow DOM 冒泡时,调用event.composedPath()将返回事件经过的节点数组。
在阴影树的内部节点上触发的自定义 DOM 事件不会冒泡出阴影边界,除非使用 composed: true标志创建事件:
- // <fancy-tab> 自定义元素类定义里面
- selectTab() {
- const tabs = this.shadowRoot.querySelector('#tabs');
- tabs.dispatchEvent(new Event('tab-select', {bubbles: true, composed: true}));
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。