多根节点组件
Vue.js 3.x 版本不再对组件的
template
选项进行“唯一根节点”的限制,而是允许
DOM
结构具备多个根节点,即下列结构的全局组件在 Vue.js 3.x
版本中是允许的。
app.component('my-box',{
template:`
<div class="first">第一个节点</div>
<div class="second">第二个节点</div>
<div class="third">第三个节点</button>
`
})
本节对这种结构可能出现的问题以及
Vue
框架的语法要求进行讲解。
1、 非 Prop 属性
在使用组件时,被子组件通过 props
选项接收的属性被称为“
Prop
属性”,没有被子组件的
props
选项接收的属性被称为“非 Prop
属性”。
Vue
框架规定:
Prop
属性可以向子组件数据区那样进行调用,即使用 this
就可以访问到
Prop
属性;非
Prop
属性需要在子组件中使用
this.$attrs
来进行访问。
【示例 4-15
】
非
Prop
属性的访问
。注册一个全局组件名为
my-phone
,其中设置一个
Prop
属性名为brand,再设置一个非
Prop
属性名为
count
。最终在组件的
template
选项中设置两个
div
容器访问这两个属性。
HTML
文档中对
my-phone
组件使用的代码如下所示。
<div id="app">
<my-phone brand="HuaWei" :count="150"></my-phone>
</div>
Vue
代码如下所示。
01 let app=Vue.createApp({})
02 app.component('my-phone',{
03 template:`
04 <div class="brand">品牌:{{this.brand}}</div>
05 <div class="count">数量:{{this.$attrs.count}}</div>
06 `,
07 props:['brand']
08 })
09 app.mount('#app')
上述代码中,<my-phone>
在
HTML
代码中设置了两个参数:
brand
和
count
。在组件注册的代码中,第07 行将
brand
参数利用
props
选项进行接收,形成了
Prop
属性。而
count
参数没有用
props
选项进行接收,即是一个非 Prop
属性。
在组件的 template
模板中,第
04
行访问了
Prop
属性
brand
,使用的是
this.brand
格式。第
05
行访问了非 Prop
属性
count
,使用的是
this.$attrs.count
格式。
这里需要注意,非 Prop
属性也是单向数据流传递,即不能在组件注册代码中改变该属性的值,也不能将该变量通过 v-model
绑定在表单元素中。
同理,在使用组件时绑定的自定义事件,也可以通过 this$attrs
获取。与非
Prop
属性不同的是,绑定事件的名称,在 this.$attrs
中需要添加
on_
前缀。
【示例 4-16
】
通过
this.$attrs
触发组件的事件
。注册一个全局组件名为
my-alert
,为该组件设置一个名为 message
的属性,在绑定一个名为
custom
的自定义事件。
HTML
文档中对
my-alert
组件使用的代码如下所示。
<div id="app">
<my-alert message="非 Prop 属性的使用" @custom="custom"></my-alert>
</div>
Vue
代码如下所示。
01 let app=Vue.createApp({
02 methods:{
03 custom(value){
04 window.alert(value)
05 }
06 }
07 })
08 app.component('my-alert',{
09 template:`
10 <button @click="btnClick">单击</button>
11 `,
12 methods:{
13 btnClick(){
14 this.$attrs.onCustom(this.$attrs.message);
15 }
16 }
17 })
18 app.mount('#app')
在上述代码中,HTML
文档使用
<my-alert>
组件时,设置了一个自定义属性
message
,同时绑定了一个自定义事件 custom
。由于在组件定义的
Vue
代码中,没有使用
props
来接收自定义属性
message
,因此message 是一个非
Prop
属性。对于绑定的自定义事件,可以在组件中使用
this.$emit()
进行调用,也可以使用 this.$attrs
进行访问。
从第 14
行中可以看出,自定义事件
custom
在
this.$attrs
对象中存储为
onCustom
,这是读者需要注意的地方,触发该事件时,需要使用 this.$attrs.onCustom()
来实现。
2、 组件事件的继承
Vue.js 3.0 框架规定:父组件的事件可以直接继承给具备该事件的子组件及其后代组件。
【示例 4-17
】
组件事件的继承
。书写一个用于选择专业的组件
select-specialty
。该组件中具备一个可供选择专业的下拉菜单。当用户选择某个专业的菜单项时,需要用弹窗在页面中显示选中的专业内容。
从本示例的要求来看,应该为组件中的下拉菜单绑定 input
或
change
事件,为了体现出组件事件的可传递性,我们将预计绑定到下拉菜单的 input
事件绑定在该组件的父组件中。测试该功能是否能够实现。
HTML
文档中对
select-specialty
组件使用的代码如下所示。
<div id="app">
<select-specialty @input="boxInput"></select-specialty>
</div>
Vue
代码如下所示。
01 let app=Vue.createApp({
02 methods:{
03 boxInput(){
04 window.alert(event.target.value);
05 }
06 }
07 })
08 app.component('select-specialty',{
09 template:`
10 <select v-model="specialty">
11 <option>前端工程师</option>
12 <option>PHP 工程师</option>
13 <option>Java 工程师</option>
14 <option>UI 设计师</option>
15 <option>项目架构师</option>
16 </select>
17 `,
18 data(){
19 return {
20 specialty:'UI 设计师'
21 }
22 }
23 })
24 app.mount('#app')
在上述代码中,input
事件绑定在了
HTML
文档中使用
<select-specialty>
组件的代码中,即该组件的父组件中。由于父组件中的事件子组件成员(即<select>
标记对)也有,因此该事件会传递给子组件成员。
若不希望父组件的事件传递给子组件,可以在子组件的定义代码中使用 inheritAttrs 选项,该选项取值为 false 时,this.$attrs 对象依然可以收集父组件传递过来的属性和绑定的事件,但是父组件绑定的事件不会在继承给子组件成员,即子组件成员的相应事件触发后,也不会执行任何父组件事件的代码。
若要让组件 select-specialty 进制父子组件之间的事件传递,代码如下所示。
01 app.component('select-specialty',{
02 inheritAttrs: false,
03 template:`
04 <select v-model="specialty">
05 <option>前端工程师</option>
06 <option>PHP 工程师</option>
07 <option>Java 工程师</option>
08 <option>UI 设计师</option>
09 <option>项目架构师</option>
10 </select>
11 `,
12 data(){
13 return {
14 specialty:'UI 设计师'
15 }
16 }
17 })
在上述代码中,第 02
行为组件设置了
inheritAttrs
选项,并取值为
false
,这样父组件绑定的
input
事件在第 04
行的
<select>
子组件成员中就不会起作用了。
3、 多根节点组件的规定
从 Vue.js 3.x
框架开始,组件的
template
选项可以接收多个根节点结构。在
Vue.js 2.x
框架中,对
template选项有必须是一个根节点的限制。
若组件具备多个根节点,同时父组件绑定了事件,根据默认情况下父组件的事件会继承给子组件的这一特性,Vue
框架会报出一个警告。警告内容如下所示。
[Vue warn]: Extraneous non-emits event listeners (click) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option.
上述警告的含义是:外部的(即父组件)的非触发的事件监听器(单击事件)被传递给子组件,但是不能自动继承,因为组件将渲染为片段或文本根节点。如果侦听器只是组件自定义事件的侦听器,则需要使用“emit
”选项来声明它。
上述警告的解决方案有以下两个:
禁止父组件向子组件继承事件:即在子组件中书写
inheritAttrs:false
选项。
在子组件中需要继承父组件事件的根节点上显式绑定
$attrs
,这被称为“隐式贯穿”行为。
【示例 4-18
】
多根节点组件的事件继承
。书写一个名为
root-test
的组件,其中包含三个根节点。在父组 件中绑定单击事件,显式指定第二个根节点继承父组件的事件。
HTML 文档中对
select-specialty
组件使用的代码如下所示。
<div id="app">
<root-test @click="divClick"></root-test>
</div>
Vue
代码如下所示。
01 let app=Vue.createApp({
02 methods:{
03 divClick(){
04 window.alert(event.target.textContent);
05 }
06 }
07 })
08 app.component('root-test',{
09 // inheritAttrs: false,
10 template:`
11 <div class="first">第一个根节点</div>
12 <div class="second" v-bind="$attrs">第二个根节点</div>
13 <div class="third">第三个根节点</div>
14 `
15 })
16 app.mount('#app')
Vue 框架的官网指出:与单个根节点组件不同,具有多个根节点的组件不具有自动隐式贯穿行为(
Auto fallthrough)。若未显式绑定
$attrs
,则会在运行时发出警告。
在上述代码中,第 10
行至第
14
行定义了组件中存在的三个根节点,同时父组件上绑定了
click
事件。 要想放置警告的发生,可以将第 09
行注释变为语句,禁止父组件将事件传递给子组件成员,这也是我们讲解的第一种警告解决方案。也可以像第 12
行那样,使用
v-bind
绑定
$attrs
,这样就指定了只有第二个根节点可以继承父组件的事件,同时也不会在运行时发出警告了。