需求是做一个按钮组件 可以根据onClick自动切换loading状态 初步代码如下
ts
import { ElButton } from "element-plus";
import { defineComponent, h, ref, computed, reactive } from "vue";
const AutoLoadingBtn = defineComponent({
name: "AutoLoadingBtn",
setup: (_, { attrs, slots, expose }) => {
const clickLoading = ref(false);
const onClick = async (e: MouseEvent) => {
if (typeof attrs.onClick !== "function") return;
try {
clickLoading.value = true;
await attrs.onClick(e);
} finally {
clickLoading.value = false;
}
};
const elBtn = ref();
const exposeObj = {};
const elBtnKeys = ["ref", "size", "type", "disabled", "shouldAddSpace"];
elBtnKeys.forEach((k) => {
exposeObj[k] = computed(() => elBtn.value?.[k]);
});
expose(reactive(exposeObj));
return () => (
<ElButton
{...attrs}
loading={clickLoading.value || (attrs.loading as boolean)}
onClick={onClick}
ref={elBtn}
>
{slots}
</ElButton>
);
},
});
export default AutoLoadingBtn as unknown as typeof ElButton;
组件参数的自动分类
vue会按照提供的props
对象构造组件的props
其余的参数被自动归类到attrs
对于形如@click
的参数 会被转化为onClick
并且允许通过emit
调用
emit
触发事件是没有返回值的 为了await onClick
取得attrs的原始函数
attrs、slots没有响应性
attrs.onClick
同样能取到原始的函数 但它不具有响应性
在watch、computed等函数中引用了attrs的属性 vue对其的准确性不做保证
如果需要一个参数具有响应性 应当在props里添加相应的属性
此外 在render函数中使用的attrs属性则始终可以取到最新值 因为因为它在每次组件更新的时候都会重新调用 如这段代码所示
ts
// render函数
return ()=> h(xxx, {
xxx,
loading: clickLoading.value || (attrs.loading as boolean),
},
xxx
);
重点在于直接在render函数里直接引用attrs.loading 而这种写法是错误的
js
const loading = computed(()=>clickLoading.value || (attrs.loading as boolean))
return () => h(xx,{xx,loading:loading.value},xx)
对于我封装的组件 并不需要onClick具有响应性 所以直接在attrs里调用就可以了
expose的用法
expose必须在setup函数的同步部分调用 在其他地方调用无效
它决定了组件对外暴露的属性和函数 和defineExpose作用一致
它的参数必须是一个对象 而且不能直接传ref和computed 但是可以传reactive构造的变量
此外 这样写是没用的
js
const elBtn = ref();
expose(new Proxy({}, {
get: (_, key) => {
return elBtn.value?.[key]
}
}));
必须手动把所有属性罗列出来
ts相关
vue和ts相性一般 建议不要特地去管类型了 any unknown一路趟过去 逻辑没问题就行 最后导出的时候 规范一下类型就可以 比如
ts
export default Comp as unknown as ReturnType<typeof defineComponent<CompProps>>
我封装的组件api与el-button完全一致 直接采用其类型就可以
如果想使用模板语法
js
import { useAttrs, useSlots } from "vue";
const attrs = useAttrs()
const slots = useSlots()
<ElButton v-bind="attrs"></ElButton>
v-bind直接使用表示绑定这个对象的所有属性
插槽的必须手动一个个写出slot 不能这样写
ruby
<template v-for="name in Object.keys(slots)" v-slot="name" :key="name">
<component :is="slots[name]"></component>
</template>