什么是插槽
在某些情况下,我们希望父组件能够传递一些内容给子组件显示,这就是插槽。
我们假设有个父组件<FancyButton>
:
<FancyButton>
Click me! <!-- 插槽内容 -->
</FancyButton>
而其模板长这样:
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
而通过插槽<slot>
其最终效果长这样:
<button class="fancy-btn">Click me!</button>
插槽内容可以是任意合法模板内容,不限文本。甚至是多种元素和组件。
<FancyButton>
<span style="color:red">Click me!</span>
<AwesomeIcon name="plus" />
</FancyButton>
渲染作用域
插槽的内容可以访问到父组件的数据作用域,因为插槽内容本身就是在父组件模板中定义的。但是,查早内容无法直接访问子组件的数据。如何间接访问到见后方作用域插槽的内容。
默认内容
也是上方的例子,在父组件中没提供插槽内容,而子组件有<slot>
时,可以提供默认内容。
<button type="submit">
<slot>
Submit <!-- 默认内容 -->
</slot>
</button>
当然如果父组件提供了插槽内容,那么子组件中提供的默认内容就会被替换。
具名插槽
顾名思义,就是每个插槽有具体的名称。
用途也很明显,这种情况只有拥有多个插槽时才用到。我们希望每个插槽都有其放置在子组件中的位置,那到底放到那,就需要名字来指引了。
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
像上方中间这种没有带name
的slot
出口会隐式地命名为“default”。
在父组件中,这样用:
<BaseLayout>
<template v-slot:header>
<!-- header 插槽的内容放这里 -->
</template>
</BaseLayout>
v-slot
可以简写成#
也就是说,上述代码可以写成下方形式:
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
上面的还能写成下面这种形式,因为所有位于顶级的非 <template>
节点都被隐式地视为默认插槽的内容。
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<!-- 隐式的默认插槽 -->
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
作用域插槽
上面提到过插槽的内容无法访问到子组件的状态。
然而在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
简单点,就是作用域插槽可以实现访问子组件的数据。
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
也可以在v-slot
中使用解构:
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
具名作用域插槽
就是又有名字、又有作用域插槽的功能(获取子组件传递的数据)
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
向具名插槽中传入props:
<slot name="header" message="hello"></slot>
注意插槽上的 name 是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽。因此最终 headerProps
的结果是 { message: 'hello' }
。
如果同时使用了具名插槽和默认插槽,则需要为默认插槽使用显示的<templete>
标签。这是为了避免因默认插槽的props的作用域而困惑。比如:
<!-- 该模板无法编译 -->
<template>
<MyComponent v-slot="{ message }">
<p>{{ message }}</p>
<template #footer>
<!-- message 属于默认插槽,此处不可用 -->
<p>{{ message }}</p>
</template>
</MyComponent>
</template>
而正确写法应该是:
<template>
<MyComponent>
<!-- 使用显式的默认插槽 -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</MyComponent>
</template>
高级列表组件示例
有这样一种高级写法,有一个<FancyList>
组件,它会渲染一个列表,并同时会封装一些加载远端数据的逻辑、使用数据进行列表渲染、或者是像分页或无限滚动这样更进阶的功能。
然而我们希望它能够保留足够的灵活性,将对单个列表元素内容和样式的控制权留给使用它的父组件。
简单地说,把内容的获取与展示工作以及页面控制工作都交给父组件,父组件做第一消费者。
<FancyList :api-url="url" :per-page="10">
<template #item="{ body, username, likes }">
<div class="item">
<p>{{ body }}</p>
<p>by {{ username }} | {{ likes }} likes</p>
</div>
</template>
</FancyList>
<ul>
<li v-for="item in items">
<slot name="item" v-bind="item"></slot>
</li>
</ul>
小提示:没有参数的 v-bind
会将一个对象的所有属性都作为 attribute 应用到目标元素上。
网友评论