二次封装
我们开发中经常需要封装一些第三方组件,那么父组件应该怎么传值,怎么调用封装好的组件原有的属性、插槽、方法,一个个调用虽然可行,但十分麻烦,我们一起来看更简便的方法。
二次封装组件,属性怎么传
attrs 主要接收不在 props 里定义,但父组件又传过来的属性,通过 v-bind=“$attrs”,可以将属性全部传给封装起来的组件(如下面例子中的 el-input ),而不需要一个一个传
// 父组件
<template>
<div>
<MyInput v-model="text" placeholder="请输入地址"></MyInput>
</div>
</template>
<script setup>
import MyInput from './components/MyInput.vue';
import { ref } from 'vue';
const text = ref('123');
</script>
// MyInput 组件
<template>
<div class="item">
<el-input v-bind="$attrs"></el-input>
</div>
</template>
<script setup>
import { onMounted, useAttrs } from 'vue';
const props = defineProps({
// placeholder:{
// type:String
// }
// attrs包含的是不在props中的属性
// 如果这里有placeholder,下面输出attrs就不会有这个placeholder
});
const attrs = useAttrs(); // 需要引入 vue 中的 useAttrs ,调用 useAttrs 获取 attrs
onMounted(() => {
console.log(attrs); // 输出如下图
})
</script>
监听事件怎么实现
$listeners :包含了父作用域中(不含 .native 修饰器的)v-on 事件监听器,他可以通过 v-on=“listeners” 传入内部组件,监听内部组件的所有事件.
<template>
<div>
<MyInput
v-model="text"
placeholder="请输入地址"
ref="focusRef"
@input="inputNum"
>
<template #prepend="{}">
<el-select v-model="select" placeholder="Select" style="width: 115px">
<el-option label="A" value="1" />
<el-option label="B" value="2" />
<el-option label="C" value="3" />
</el-select>
</template>
<template #append>
<el-button :icon="Search" />
</template>
</MyInput>
</div>
</template>
<script setup>
import MyInput from "./components/MyInput.vue";
import { onMounted, ref } from "vue";
import { Search } from "@element-plus/icons-vue";
const text = ref("123");
const select = ref("");
const focusRef = ref();
onMounted(() => {
focusRef.value.focus();
});
const inputNum = (val) => { // 被监听事件触发时调用的方法
console.log("输出:" + val);
};
</script>
// 子组件
<template>
<div class="item">
<el-input v-bind="$attrs" v-on="$listeners" ref="inp">
<template v-for="(value, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}"></slot>
</template>
</el-input>
</div>
</template>
<script setup>
import { onMounted, useSlots, useAttrs, nextTick, ref } from "vue";
const attrs = useAttrs();
const slots = useSlots();
const inp = ref();
onMounted(() => {
console.log(attrs);
console.log(slots);
console.log(inp.value);
});
defineExpose(
new Proxy(
{},
{
// 使用 Proxy 代理暴露出去
get(_target, prop) {
return inp.value?.[prop];
},
has(_target, prop) {
return prop in inp.value;
},
}
)
);
</script>
插槽怎么使用
二次封装组件时经常需要往原组件的插槽中传递内容,这时要让原始组件知道我们使用了哪些插槽,可以使用 $slots。
$slots 是一个表示父组件所传入[插槽]的对象,我们可以在子组件中通过 $slots 获取到父组件传过来所有插槽名,接下来子组件遍历 $slots 动态渲染插槽即可
// 父组件
<template>
<div>
<MyInput v-model="text" placeholder="请输入地址">
<template #prepend="{}">
<el-select v-model="select" placeholder="Select" style="width: 115px">
<el-option label="A" value="1" />
<el-option label="B" value="2" />
<el-option label="C" value="3" />
</el-select>
</template>
<template #append>
<el-button :icon="Search" />
</template>
</MyInput>
</div>
</template>
<script setup>
import MyInput from './components/MyInput.vue';
import { ref } from 'vue';
import { Search } from '@element-plus/icons-vue'
const text = ref('123');
const select = ref('')
</script>
// 也可以往插槽传值 slotData
<template>
<div class="item">
<el-input v-bind="$attrs">
<template v-for="(value,name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}"></slot>
</template>
</el-input>
</div>
</template>
<script setup>
import { onMounted, useSlots, useAttrs } from 'vue';
const attrs = useAttrs();
const slots = useSlots() // 引入 useSlots
onMounted(() => {
console.log(attrs);
console.log(slots); // 输出如下图
})
</script>
父组件的 ref 怎么调用目标组件内部方法
我们要想通过父组件的 ref 调用到子组件内部方法(如 el-input 的 focus 方法)可以怎么做?其实可以通过 ref 链式调用,比如 focusRef.value.inp.value.focus(),但代码的可读性差
更好的方法是将所有方法暴露出去供父组件调用,可以利用 Proxy 对象来创建一个代理,并通过 defineExpose 将这个代理暴露给父组件。这个代理的目的是拦截对特定属性的访问,并将这些访问重定向到 inp.value(即 el-input 的引用)上,这样,父组件就可以通过组件的 ref 访问到 el-input 实例的属性
// 父组件
<template>
<div>
<MyInput v-model="text" placeholder="请输入地址" ref="focusRef">
<template #prepend="{}">
<el-select v-model="select" placeholder="Select" style="width: 115px">
<el-option label="A" value="1" />
<el-option label="B" value="2" />
<el-option label="C" value="3" />
</el-select>
</template>
<template #append>
<el-button :icon="Search" />
</template>
</MyInput>
</div>
</template>
<script setup>
import MyInput from "./components/MyInput.vue";
import { onMounted, ref } from "vue";
import { Search } from "@element-plus/icons-vue";
const text = ref("123");
const select = ref("");
const focusRef = ref();
onMounted(() => {
focusRef.value.focus();
});
</script>
// 子组件
<template>
<div class="item">
<el-input v-bind="$attrs" ref="inp">
<template v-for="(value, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}"></slot>
</template>
</el-input>
</div>
</template>
<script setup>
import { onMounted, useSlots, useAttrs, nextTick, ref } from "vue";
const attrs = useAttrs();
const slots = useSlots();
const inp = ref();
onMounted(() => {
console.log(attrs);
console.log(slots);
console.log(inp.value); // 这里输出如下图
});
defineExpose( // 使用 Proxy 代理暴露出去
new Proxy(
{},
{
get(_target, prop) {
return inp.value?.[prop];
},
has(_target, prop) {
return prop in inp.value;
},
}
)
);
</script>