一般在项目中,我们直接使用第三方库,比如element-ui,或者是一些其它的库,这些库中早就写好了他们自己有的组件。
在实际开发项目中,我们可能需要根据业务来进行对第三方库组件的封装,从而实现一个符合自己业务的封装组件,本质上而言就是进行组件的二次封装。
对于组件的二次封装,我们需要了解属性透传的概念,大概意思就是我们调用的组件传递的属性直接传递到第三方库组件中,数据流的方向是这样的:前端调用传递属性数据->已经二次封装的组件的属性数据->第三方原生组件属性数据
。
$attrs
这个属性在二次封装组件中非常有用,表示的是父组件传递给子组件的属性,但是传递的属性没有被子组件显示声明在props中。
xml
//ParentComponent
<template>
<child-component message="Hello from parent" custom-attribute="customValue"></child-component>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent,
},
};
</script>
xml
//ChildComponent
<template>
<div>
<p>{{ message }}</p>
<p>{{ customAttribute }}</p>
</div>
</template>
<script>
export default {
props: {
message: String,
},
created() {
console.log(this.$attrs); // 输出:{ custom-attribute: "customValue" }
},
};
</script>
在这个例子中,
ChildComponent
中的message
是一个通过props
声明的属性,而custom-attribute
则是没有在props
中声明的属性。当ParentComponent
传递这两个属性给ChildComponent
时,custom-attribute
会被自动合并到$attrs
对象中。
inheritAttrs
inheritAttrs
在父子组件中也是非常常用的,默认值未true,即子组件继承父组件传递的未声明为 props
的属性,比如上面的 :
ini
<child-component message="Hello from parent"
custom-attribute="customValue"></child-component>
如果子组件中props没有进行显示的声明custom-attribute属性,如果inheritAttrs为false,那么子组件的根部元素是不会继承这个属性的,反之则会继承。
如果你设置了
inheritAttrs: false
,那么默认情况下,Vue 不会将父组件传递的未声明为 props
的属性合并到子组件的根元素上。
当你使用 inheritAttrs: true
时,Vue 会将所有未在子组件中声明为 props
的属性自动应用到子组件的根元素上。这就意味着你在子组件内部直接可以使用这些属性,而不需要额外使用 $attrs
。
xml
<!-- ParentComponent.vue -->
<template>
<div>
<child-component custom-attribute="customValue" message="Hello from parent"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent,
},
};
</script>
xml
<!-- ChildComponent.vue -->
<template>
<div>
<p>{{ message }}</p>
<p>{{ customAttribute }}</p>
</div>
</template>
<script>
export default {
props: {
message: String,
},
created() {
// 因为 inheritAttrs: true,custom-attribute 会直接应用到子组件的根元素上
console.log(this.$attrs); // 输出:{ custom-attribute: "customValue" }
},
};
</script>
如果
inheritAttrs
被设置为false
,那么未在props
中声明的属性将不会自动绑定到子组件的根元素上。因此,你将无法直接在子组件的模板中使用{{ customAttribute }}
xml
<!-- ChildComponent.vue -->
<template>
<div>
<p>{{ message }}</p>
<p>{{ $attrs['custom-attribute'] }}</p>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
message: String,
},
created() {
console.log(this.$attrs); // 输出:{ custom-attribute: "customValue" }
},
};
</script>
v-on="$listeners"
Vue.js 中的一个语法糖,用于将父组件传递给子组件的所有事件监听器绑定到子组件的根元素上。本质上是为了代码的美观,代码更加易读易写,本身不会引入新的功能。
这样做的目的是为了方便地将父组件中的事件监听器应用到子组件上,而无需在子组件中显式声明这些事件。
比如下面显示声明:
xml
<!-- ParentComponent.vue -->
<template>
<div>
<child-component @custom-event="handleCustomEvent"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent,
},
methods: {
handleCustomEvent() {
console.log('Custom event handled in parent component');
},
},
};
</script>
需要专门写一个事件来触发emit
而如果使用了这种语法糖,那么:
xml
<!-- ChildComponent.vue -->
<template>
<div @custom-event="handleCustomEvent">
<button @click="$emit('custom-event')">Trigger Custom Event</button>
</div>
</template>
<script>
export default {
methods: {
handleCustomEvent() {
console.log('Custom event handled in child component');
},
},
};
</script>
是不是感觉到了v-on="listeners"的一部分用处?上面的示例只是两个组件之间的调用,所以还不能很好的体现它的好处,当在三个组件,甚至四个,五个或者更多组件之间进行事件监听响应以及传递的时候,它的好处就显示出来了,比如下面这个示例: 使用v-on="listeners"示例:
xml
<!-- 爷父子三个组件 -->
<!-- GrandparentComponent.vue -->
<template>
<div>
<parent-component @custom-event="handleCustomEvent"></parent-component>
</div>
</template>
<script>
import ParentComponent from './ParentComponent.vue';
export default {
components: {
ParentComponent,
},
methods: {
handleCustomEvent() {
// 处理来自父组件的事件
},
},
};
</script>
<!-- ParentComponent.vue -->
<template>
<div>
<child-component v-on="$listeners"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent,
},
};
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<!-- 透传父组件的事件到子组件 -->
<grandchild-component v-on="$listeners"></grandchild-component>
</div>
</template>
<script>
import GrandchildComponent from './GrandchildComponent.vue';
export default {
components: {
GrandchildComponent,
},
};
</script>
<!-- GrandchildComponent.vue -->
<template>
<div @click="$emit('custom-event')">Click me</div>
</template>
<!-- ... -->
不使用
xml
<!-- GrandparentComponent.vue -->
<template>
<div>
<ParentComponent @custom-event="handleCustomEvent"></ParentComponent>
</div>
</template>
<script>
import ParentComponent from './ParentComponent.vue';
export default {
components: {
ParentComponent,
},
methods: {
handleCustomEvent() {
// 处理来自父组件的事件
},
},
};
</script>
<!-- ParentComponent.vue -->
<template>
<div>
<ChildComponent @custom-event="handleCustomEvent"></ChildComponent>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent,
},
methods: {
handleCustomEvent() {
// 透传事件给父组件
this.$emit('custom-event');
},
},
};
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<GrandchildComponent @custom-event="handleCustomEvent"></GrandchildComponent>
</div>
</template>
<script>
import GrandchildComponent from './GrandchildComponent.vue';
export default {
components: {
GrandchildComponent,
},
methods: {
handleCustomEvent() {
// 透传事件给父组件
this.$emit('custom-event');
},
},
};
</script>
<!-- GrandchildComponent.vue -->
<template>
<div @click="handleClick">Click me</div>
</template>
<script>
export default {
methods: {
handleClick() {
// 透传事件给父组件
this.$emit('custom-event');
},
},
};
</script>
非常一个明显的特点就是当涉及两个组件以上的事件监听和传递时,我们可以发现使用v-on="listeners"只需要在中间组件上写上这个语法糖即可,而对于不使用这个语法糖的组件,对于
中间的组件
必须要一直进行同一个事件的监听重复编写.
所以这也是为什么使用语法糖可以解决代码美观问题。而对于第三方组件的封装,因为我们需要对事件保持原有的名字,所以直接使用v-on="$listenter"解决事件透传问题
。
这其实也算是一种中间商的设计方式吧。