在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>
相关推荐
开开心心就好1 小时前
免费批量文件重命名软件
vue.js·人工智能·深度学习·typescript·pdf·excel·less
老马啸西风10 小时前
工作流引擎-18-开源审批流项目之 plumdo-work 工作流,表单,报表结合的多模块系统
vue.js·开源·activiti·workflow·flowable·oa·bpm
sunbyte11 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Form Wave(表单label波动效果)
前端·javascript·css·vue.js·tailwindcss
每天都想着怎么摸鱼的前端菜鸟12 小时前
uniapp开发app 实现简易的路由拦截器
vue.js·uni-app
琢磨先生TT13 小时前
我用 54000 分钟打造的后台系统模板,开源了!
前端·vue.js·前端框架
用户261245834016113 小时前
vue学习路线(8.事件处理)
前端·vue.js
我血条子呢13 小时前
[Vue2]判断引用组件是否注册了 $emit 对应的事件
前端·javascript·vue.js
sunbyte13 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Sound Board(音响控制面板)
前端·javascript·vue.js·ecmascript·tailwindcss
wxid:yiwoxuan13 小时前
房屋租赁系统 Java+Vue.js+SpringBoot,包括房屋信息、看房申请、租赁合同、房屋报修、收租信息、维修数据、租客管理、公告管理模块
java·vue.js·课程设计
乐予吕13 小时前
从 scoped 到 @scope:CSS 样式隔离的进化
前端·css·vue.js