组件封装 1 组件的二次封装
大约 3 分钟
本文旨在总结在 vue 中的封装基础逻辑思路。
组件的二次封装
在 Vue 中,对组件进行二次封装主要涉及三个方面:属性(Attributes)、插槽(Slots)以及引用(Ref)的封装。
1. 属性的封装
利用 Vue 的 $attrs
,可以轻松获取父组件传递的所有属性,通过使用 v-bind
,可以将所有属性绑定到子组件上,无需逐个添加。
<template>
<BaseComponent v-bind="$attrs" />
</template>
<script>
export default {}
</script>
2. 插槽的封装
类似于属性的处理方式,利用 $slots
属性获取父组件传递的插槽。通过循环遍历插槽,可以对它们进行二次封装。
<template>
<BaseComponent v-bind="$attrs">
<template v-for="(value, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}"></slot>
</template>
</BaseComponent>
</template>
<script>
export default {}
</script>
3. 引用的封装
引用的封装相对较为复杂。使用 ref 的目的是为了获取子组件内部的方法和数据,虽然这并不是推荐的做法,但也可作为备选方案。
在这里,我们将封装组件中的所有 ref 属性值提取到外部,从而可以访问原组件中的方法和数据。
<template>
<BaseComponent ref="refCom" v-bind="$attrs">
<template v-for="(value, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}"></slot>
</template>
</BaseComponent>
</template>
<script>
export default {
mounted() {
const entries = Object.entries(this.$refs.refCom)
for (const [key, value] of entries) {
this[key] = value
}
},
}
</script>
通过这三个方面的封装,可以更灵活地使用和定制 Vue 组件,提高代码的可维护性和复用性。
封装组件的传参
在封装组件时,常会用到组件传参,但是参数可能会在子组件中改变。通常的办法是采用 v-model
,但是在二次封装组件时,这样会打破单向数据流的传递。此时我们可以采用 computed 计算属性进行传参。
先看原本的代码:
<!-- 父组件 -->
<template>
<div>
<searchBar v-model="searchData" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import searchBar from './searchBar.vue'
const searchData = ref({
keyword: '',
options: [
{ label: 'label1', value: 'label1' },
{ label: 'label2', value: 'label2' },
],
selectedValue: 'select',
})
</script>
<!-- 子组件 -->
<template>
<el-input v-model="modelValue.keyword">
<template #prepend>
<el-select v-model="modelValue.selectedValue">
<el-option
v-for="item in modelValue.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-input>
</template>
<script setup>
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
})
</script>
初步处理:先将 v-model 拆分为 modelValue
数据绑定和 @update:modelValue
数据更新事件。
<template>
<el-input
:modelValue="modelValue.keyword"
@update:modelValue="handleKeywordChange"
>
<template #prepend>
<el-select v-model="modelValue.selectedValue">
<el-option
v-for="item in modelValue.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-input>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
})
const emit = defineEmits(['update:modelValue'])
const handleKeywordChange = (value) => {
emit('update:modelValue', {
...props.modelValue,
keyword: value,
})
}
</script>
改进处理: 利用 computed 计算数据对父组件传递过来的值进行包装,利用 get 和 set 进行传递。此时依旧改用 v-model 进行绑定 computed 的计算值。
<template>
<el-input v-model="keyword">
<template #prepend>
<el-select v-model="modelValue.selectedValue">
<el-option
v-for="item in modelValue.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-input>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
})
const emit = defineEmits(['update:modelValue'])
const keyword = computed({
get() {
return props.modelValue.keyword
},
set(value) {
emit('update:modelValue', {
...props.modelValue,
keyword: value,
})
},
})
</script>
但是这样有一个缺点,即所有的属性都要用 computed 进行一次转换,过于繁琐。因此,可以采用 ES6 中 Proxy 对象进行代理。
<template>
<el-input v-model="model.keyword">
<template #prepend>
<el-select v-model="model.selectedValue">
<el-option
v-for="item in model.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-input>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
})
const emit = defineEmits(['update:modelValue'])
const model = computed({
get() {
return new Proxy(props.modelValue, {
set(obj, name, val) {
emit('update:modelValue', {
...obj,
[name]: val,
})
return true
},
})
},
set(value) {
emit('update:modelValue', value)
},
})
</script>
进一步封装:
import { computed } from 'vue'
export function useModel(props, propName, emit) {
return computed({
get() {
return new Proxy(props[propName], {
set(obj, name, val) {
emit(`update:${propName}`, {
...obj,
[name]: val,
})
return true
},
})
},
set(value) {
emit(`update:${propName}`, value)
},
})
}
Loading...