在vue中封装通用组件的注意事项

需求是做一个按钮组件 可以根据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>
相关推荐
90后的晨仔3 小时前
🚨XSS 攻击全解:什么是跨站脚本攻击?前端如何防御?
前端·vue.js
90后的晨仔3 小时前
Vue 模板语法完全指南:从插值表达式到动态指令,彻底搞懂 Vue 模板语言
前端·vue.js
90后的晨仔4 小时前
Vue 3 应用实例详解:从 createApp 到 mount,你真正掌握了吗?
前端·vue.js
SY_FC4 小时前
uniapp input 聚焦时键盘弹起滚动到对应的部分
javascript·vue.js·elementui
爱吃香蕉的阿豪4 小时前
SignalR 全解析:核心原理、适用场景与 Vue + .NET Core 实战
vue.js·microsoft·c#·.netcore·signalr
OpenTiny社区7 小时前
把 SearchBox 塞进项目,搜索转化率怒涨 400%?
前端·vue.js·github
ReturnTrue8688 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
武昌库里写JAVA9 小时前
「mysql」Mac osx彻底删除mysql
vue.js·spring boot·毕业设计·layui·课程设计
Rika9 小时前
手写mini-vue之后,我写了一份面试通关手册
前端·vue.js
咔咔一顿操作10 小时前
常见问题三
前端·javascript·vue.js·前端框架