一、插槽 Slots
1.1 用法
在某些场景下 我们可能想要为子组件传递一些模板片段 让子组件在他们的组件中渲染这些内容
比如:
<FancyButton>
Click me! <!-- 插槽内容 -->
</FancyButton>
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
//最后渲染成
<button class="fancy-btn">Click me!</button>
<slot>
元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。
1.2 渲染作用域
插槽可以访问到父组件的数据作用域 想干 插槽内容无法访问子组件的数据 vue模板中的表达式只能访问其定义时所处的作用域,这和JavaScript的词法作用域规则是一致的。
1.3 默认插槽 和 具名插槽
对于某些场景,需要多个插槽出口 <slot>
元素可以有一个特殊的 attribute name
,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容。带name的插槽被称为具名插槽 没有name的会隐式地命名为“default”
要为具名插槽传入内容,我们需要使用一个含 v-slot
指令的 <template>
元素,并将目标插槽的名字传给该指令: v-slot
有对应的简写 # <template v-slot:header>
可以简写为 <template #header>
<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>
1.4 动态插槽名
动态指令参数在v-slot 上也是有效的 如:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
1.5 作用域插槽
我们知道 插槽其实是无法访问到子组件里面数据以及状态的 但是有些场景下就是需要呢 下面有一个方法 可以像组件传递props那样,向一个插槽的出口上传递attributes :
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
当需要接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别。下面我们将先展示默认插槽如何接受 props,通过子组件标签上的 v-slot
指令,直接接收到了一个插槽 props 对象:
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
//也可以解构
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
1.6 具名作用域插槽
插槽 props 可以作为 v-slot
指令的值被访问到:v-slot:name="slotProps"
。
插槽上的 name
是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽。因此最终 headerProps
的结果是 { message: 'hello' }
。
<!-- 该模板无法编译 -->
<template>
<MyComponent v-slot="{ message }">
<p>{{ message }}</p>
<template #footer>
<!-- message 属于默认插槽,此处不可用 -->
<p>{{ message }}</p>
</template>
</MyComponent>
</template>
为默认插槽使用显式的 <template>
标签有助于更清晰地指出 message
属性在其他插槽中不可用:
<template>
<MyComponent>
<!-- 使用显式的默认插槽 -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</MyComponent>
</template>
二、依赖注入
2.1 了解
正常情况下 当我们需要父组件想子组件传递数据时 会用到props 但是当我们需要多层级嵌套的组件 那么就会很麻烦,有子孙组件出来的话 props就会一层一层的往下传递 这个问题我们称之为“prop逐级透传”。
provide 和 inject 可以帮助我们解决这个问题 一个父组件相对于其所有的后代组件 会作为依赖提供者
2.2 provide(提供)
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
除了在一个组件中提供依赖 我们还可以在整个应用层提供依赖:
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
在应用级别提供的数据在该应用内的所有组件中都可以注入
2.3 inject
要注入上层组件提供的数据 需要inject()函数:
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。
3.3和响应式数据配合使用
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
有的时候,我们可能需要在注入方组件中更改数据。在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
最后,如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
三、异步组件
3.1基本用法
在大型项目中 我们可能需要拆分应用为更小的块 并仅在需要时再从服务器加载相关组件 vue提供了defineAsyncComponent 方法来实现此功能:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务器获取组件
resolve(/* 获取到的组件 */)
})
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
ES 模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和 defineAsyncComponent
搭配使用。
<script setup>
import { defineAsyncComponent } from 'vue'
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
<template>
<AdminPage />
</template>
与普通组件一样,异步组件可以使用 app.component()
全局注册:
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
3.2加载与错误状态
异步操作不可避免地会涉及到加载和错误状态,因此 defineAsyncComponent()
也支持在高级选项中处理这些状态:
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
四、透传 Attributes
4.1了解
透传其实就是传递给一个组件,却没有被组件声明props或者emits的attribute或者v-on事件监听器。比如class style id 等
注意:透传的 attribute 不会包含 <MyButton>
上声明过的 props 或是针对 emits
声明事件的 v-on
侦听函数,换句话说,声明过的 props 和侦听函数被 <MyButton>
“消费”了
如果透传符合声明 也可以作为props传入组件
4.2 禁用attributes 继承
如果你不想再组件自动继承 attribute 你可以在组件选项中设置 inheritAttrs: false
。
如果你使用了 <script setup>
,你需要一个额外的 <script>
块来书写这个选项声明
<script>
// 使用普通的 <script> 来声明选项
export default {
inheritAttrs: false
}
</script>
<script setup>
// ...setup 部分逻辑
</script>
注意:
和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像 foo-bar
这样的一个 attribute 需要通过 $attrs['foo-bar']
来访问
像 @click
这样的一个 v-on
事件监听器将在此对象下被暴露为一个函数 $attrs.onClick
。