在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>
相关推荐
古夕7 小时前
第三方 SSO 接入实践:redirect_uri 编码、回调一致性与跨项目联调
前端·vue.js
Ruihong7 小时前
Vue withDefaults 转 React:VuReact 怎么处理?
vue.js·react.js·面试
稀土熊猫君9 小时前
一个人能做出什么开源项目?
vue.js·后端·开源
DarkLONGLOVE1 天前
快速上手 Pinia!Vue3 极简状态管理使用教程
javascript·vue.js
宸翰1 天前
解决 uni-app App 端 vue-i18n 占位符丢失:封装跨端可用的 tf 格式化方法
前端·vue.js·uni-app
用户2136610035722 天前
VueRouter进阶-动态路由与嵌套路由
前端·vue.js
暴走的小呆2 天前
Vue 2 中 Object 的变化侦测:从 getter/setter 到 Dep、Watcher、Observer
vue.js
英勇无比的消炎药2 天前
TinyVue v-auto-tip: 文本超长自动提示的优雅方案
vue.js
时光足迹2 天前
腾讯云 TRTC UniApp SDK 从入门到上线
前端·vue.js·uni-app
时光足迹2 天前
uni-app 里把加密视频嵌入页面播放?我分析了 4 种方案,只有 1 种接近完美
前端·vue.js·uni-app