组件的最大特性就是 重用 ,而用好插槽能大大提高组件的可重用能力。
**插槽的作用:**父组件向子组件传递内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JI8FZpIm-1672136742834)(assets/34.png)]
通俗的来讲,插槽无非就是在 子组件 中挖个坑,坑里面放什么东西由 父组件 决定。
插槽类型有:
在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。
这里有一个
组件,可以像这样使用:
Click me!
而
的模板是这样的:
元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WTdnwc4R-1672136742834)(assets/slots.dbdaf1e8.png)]
最终渲染出的 DOM 是这样:
完整案例:06_slot/42_slot.html
插槽
点击 注册 登录
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。举例来说:
{{ message }}
{{ message }}
这里的两个 {{ message }}
插值表达式渲染的内容都是一样的。
插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:
父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
完整案例:06_slot/43_slot_render_scope.html
插槽
{{ msg }}{{ msg }}
在外部没有提供任何内容的情况下,可以为插槽指定默认内容。比如有这样一个
组件:
如果我们想在父组件没有提供任何插槽内容时在 内渲染“Submit”,只需要将“Submit”写在
标签之间来作为默认内容:
现在,当我们在父组件中使用
且没有提供任何插槽内容时:
“Submit”将会被作为默认内容渲染:
但如果我们提供了插槽内容:
Save
那么被显式提供的内容会取代默认内容:
完整案例:06_slot/44_slot_default.html
插槽
点击 注册
有时在一个组件中包含多个插槽出口是很有用的。举例来说,在一个
组件中,有如下模板:
对于这种场景,
元素可以有一个特殊的 attribute name
,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容:
这类带 name
的插槽被称为具名插槽 (named slots)。没有提供 name
的
出口会隐式地命名为“default”。
在父组件中使用
时,我们需要一种方式将多个插槽内容传入到各自目标插槽的出口。此时就需要用到具名插槽了:
要为具名插槽传入内容,我们需要使用一个含 v-slot
指令的 元素,并将目标插槽的名字传给该指令:
v-slot
有对应的简写#
,因此可以简写为
。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oKs14cfJ-1672136742835)(assets/named-slots.ebb7b207.png)]
完整案例:06_slot/45_slot_name.html
具名插槽
首页头部首页内容首页底部 分类头部分类内容分类底部
头部 内容
动态指令参数在 v-slot
上也是有效的,即可以定义下面这样的动态插槽名:
......
注意这里的表达式和动态指令参数受相同的语法限制。
完整案例:06_slot/46_dynamic_slot_name.html
46_动态插槽名
{{ type }} 父组件默认值
1 动态插槽名 子组件默认值 1
2 动态插槽名 子组件默认值 2
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRQTSeCb-1672136755386)(null)]
完整案例:06_slot/47_scope_slot.html
插槽
{{ msg }} -- {{ slotProps.str }} -- {{ slotProps.num }}
一个表示父组件所传入插槽的对象。
通常用于手写渲染函数,但也可用于检测是否存在插槽。
每一个插槽都在 this.$slots
上暴露为一个函数,返回一个 vnode 数组,同时 key 名对应着插槽名。默认插槽暴露为 this.$slots.default
。
如果插槽是一个作用域插槽,传递给该插槽函数的参数可以作为插槽的 prop 提供给插槽。
在渲染函数中,可以通过 this.$slots 来访问插槽:
完整案例:06_slot/48_$slot.html
48_$slots渲染函数
11112222
默认值 底部默认值
vue2渲染函数参照
vue2渲染函数
com
test
通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJRb14me-1672136742837)(assets/prop-drilling.11201220.png)]
注意,虽然这里的 组件可能根本不关心这些 props,但为了使
能访问到它们,仍然需要定义并向下传递。如果组件链路非常长,可能会影响到更多这条路上的组件。这一问题被称为“prop 逐级透传”,显然是我们希望尽量避免的情况。
provide
和 inject
可以帮助我们解决这一问题。 [1] 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3SxMhhE6-1672136742837)(assets/provide-inject.3e0505e4.png)]
要为组件后代提供数据,需要使用到 provide
选项:
{provide: {message: 'hello!'}
}
对于 provide
对象上的每一个属性,后代组件会用其 key 为注入名查找期望注入的值,属性的值就是要提供的数据。
如果我们需要提供依赖当前组件实例的状态 (比如那些由 data()
定义的数据属性),那么可以以函数形式使用 provide
:
{data() {return {message: 'hello!'}},provide() {// 使用函数的形式,可以访问到 `this`return {message: this.message}}
}
这不会使注入保持响应性(比如祖先组件中有一个count的状态,祖先组件修改完状态,后代组件默认的值没有响应式的改变)
要注入上层组件提供的数据,需使用 inject
选项来声明:
{inject: ['message'],created() {console.log(this.message) // injected value}
}
注入会在组件自身的状态之前被解析,因此你可以在 data()
中访问到注入的属性:
{inject: ['message'],data() {return {// 基于注入值的初始数据fullMessage: this.message}}
}
当以数组形式使用
inject
,注入的属性会以同名的 key 暴露到组件实例上。在上面的例子中,提供的属性名为"message"
,注入后以this.message
的形式暴露。访问的本地属性名和注入名是相同的。如果我们想要用一个不同的本地属性名注入该属性,我们需要在
inject
选项的属性上使用对象的形式:{inject: {/* 本地属性名 */ localMessage: {from: /* 注入来源名 */ 'message'}} }
这里,组件本地化了原注入名
"message"
所提供的的属性,并将其暴露为this.localMessage
。默认情况下,
inject
假设传入的注入名会被某个祖先链上的组件提供。如果该注入名的确没有任何组件提供,则会抛出一个运行时警告。如果在注入一个值时不要求必须有提供者,那么我们应该声明一个默认值,和 props 类似:
{// 当声明注入的默认值时// 必须使用对象形式inject: {message: {from: 'message', // 当与原注入名同名时,这个属性是可选的default: 'default value'},user: {// 对于非基础类型数据,如果创建开销比较大,或是需要确保每个组件实例// 需要独立数据的,请使用工厂函数default: () => ({ name: 'John' })}} }
完整案例:07_provide/49_provide_inject.html
依赖注入
父组件
子组件
{ msg }} -- {{ count }} -->{{ mymsg }} -- {{ mycount }} -- {{ mytest }}
发现以上案例在count值发生改变时没有更新后代数据
为保证注入方和供给方之间的响应性链接,我们需要使用 computed() 函数提供一个计算属性
完整案例:07_provide/50_provide_inject_computed_vue3.html
依赖注入
父组件
子组件
{ msg }} -- {{ count }} -->{{ mymsg }} -- {{ mycount }} -- {{ mytest }}
测试得知vue2中也是如此处理数据
有些场景会需要在两个组件间来回切换(Tab切换)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-43AZ6zrO-1672136742838)(assets/image-20220919113443278.png)]
用于绑定动态组件。
完整案例:08_dynamic/51_dynamic_component.html
动态组件
- 首页
- 分类
- 购物车
- 我的
如果此时给每个组件加入一个输入框,输入内容切换组件查看效果,发现切换回来数据不在
组件缓存包裹在其中的动态切换组件
包裹动态组件时,会缓存不活跃的组件实例,而不是销毁它们。
任何时候都只能有一个活跃组件实例作为
的直接子节点。
完整案例:08_dynamic/52_keep-alive.html
动态组件
- 首页
- 分类
- 购物车
- 我的
当一个组件在
中被切换时,它的
activated
和deactivated
生命周期钩子将被调用,用来替代mounted
和unmounted
。这适用于的直接子节点及其所有子孙节点。
完整案例:08_dynamic/53_activated_deacvidated.html
动态组件
- 首页
- 分类
- 购物车
- 我的
要不不缓存,要缓存都缓存了,这样不好
使用
include
/exclude
可以设置哪些组件被缓存,使用max
可以设定最多缓存多少个
组件如果想要条件性地被
KeepAlive
缓存,就必须显式声明一个name
选项。完整案例:
08_dynamic/54_keep_alive_include.html
动态组件
- 首页
- 分类
- 购物车
- 我的
元素一个用于渲染动态组件或元素的“元组件”
完整案例:08_dynamic/55_component_element.html
55_component元素
你好
也可以渲染组件
当 is
attribute 用于原生 HTML 元素时,它将被当作 Customized built-in element,其为原生 web 平台的特性。
但是,在这种用例中,你可能需要 Vue 用其组件来替换原生元素,如 DOM 模板解析注意事项所述。你可以在 is
attribute 的值中加上 vue:
前缀,这样 Vue 就会把该元素渲染为 Vue 组件(my-row-component
为自定义组件):
完整案例:08_dynamic/56_DOM模板解析注意事项.html
Document
序号 姓名
1 吴大勋
注意不要使用绑定属性
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent
方法来实现此功能
学习:defineAsyncComponent()
import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() => {return new Promise((resolve, reject) => {// ...从服务器获取组件resolve(/* 获取到的组件 */)})
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
完整案例:09_async/57_defineAsyncComponent.html
异步组件
异步加载组件
学习:() => import()
ES 模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和 defineAsyncComponent
搭配使用。类似 Vite 和 Webpack 这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue 单文件组件
import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() =>import('./components/MyComponent.vue')
)
以后讲解项目时可以用到,需要在脚手架环境中使用(单文件组件中使用
)
组件
是一个内置组件,用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。
vue3中新增的
完整案例:09_async/58_Suspense.html
异步组件
正在加载。。。
异步加载组件
后期可以和
Transition
,KeepAlive
,路由
等结合使用
除了 Vue 内置的一系列指令 (比如 v-model
或 v-show
) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。
学习:app.directive()、directives 选项
当一个 input 元素被 Vue 插入到 DOM 中后,它会被自动聚焦:
完整案例:10_directive/59_自定义指令.html
自定义指令
一个指令的定义对象可以提供几种钩子函数 (都是可选的):
vue3相比 vue2,钩子函数做了更新
vue2中一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind
:只调用一次,指令与元素解绑时调用。
// vue3钩子函数
const myDirective = {// 在绑定元素的 attribute 前// 或事件监听器应用前调用created(el, binding, vnode, prevVnode) {// 下面会介绍各个参数的细节},// 在元素被插入到 DOM 前调用beforeMount(el, binding, vnode, prevVnode) {},// 在绑定元素的父组件// 及他自己的所有子节点都挂载完成后调用mounted(el, binding, vnode, prevVnode) {},// 绑定元素的父组件更新前调用beforeUpdate(el, binding, vnode, prevVnode) {},// 在绑定元素的父组件// 及他自己的所有子节点都更新后调用updated(el, binding, vnode, prevVnode) {},// 绑定元素的父组件卸载前调用beforeUnmount(el, binding, vnode, prevVnode) {},// 绑定元素的父组件卸载后调用unmounted(el, binding, vnode, prevVnode) {}
}
指令的钩子会传递以下几种参数:
el
:指令绑定到的元素。这可以用于直接操作 DOM。binding
:一个对象,包含以下属性。 value
:传递给指令的值。例如在 v-my-directive="1 + 1"
中,值是 2
。oldValue
:之前的值,仅在 beforeUpdate
和 updated
中可用。无论值是否更改,它都可用。arg
:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo
中,参数是 "foo"
。modifiers
:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar
中,修饰符对象是 { foo: true, bar: true }
。instance
:使用该指令的组件实例。dir
:指令的定义对象。vnode
:代表绑定元素的底层 VNode。prevNode
:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate
和 updated
钩子中可用。给一个元素设置颜色 v-red v-color=“green”,设置手机号正确为绿色,不正确为红色
完整案例10_directive/60_directives_demo.html
自定义指令
自定义指令 无参数 设置为红色自定义指令 有参数 设置为绿色
自定义指令更多的用来实现DOM相关操作
上述案例中如果在vue2中,使用 inserted 代替 mounted,使用 update代替updated
自行练习vue2的上述案例
学习:插件开发与原理(app.use())
插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。下面是如何安装一个插件的示例:
import { createApp } from 'vue'const app = createApp({})app.use(myPlugin, {/* 可选的选项 */
})
一个插件可以是一个拥有 install()
方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装它的应用实例和传递给 app.use()
的额外选项作为参数:
const myPlugin = {install(app, options) {// 配置此应用}
}
插件没有严格定义的使用范围,但是插件发挥作用的常见场景主要包括以下几种:
app.component()
和 app.directive()
注册一到多个全局组件或自定义指令。app.provide()
使一个资源可被注入进整个应用。app.config.globalProperties
中添加一些全局实例属性或方法(区别vue2的场景
)完整案例:11_plugin/61_plugin_vue3.html
vue3自定义插件
{{ $tran('welcome') }} - {{ $tran('bye') }}
11_plugin/62_plugin_vue2.html
62_插件的自定义vue2
{{ $t('welcome') }} - {{ $t('bye') }}
上一篇:软件测试内容的要点