用了三年 Vue,我终于理解为什么“组件设计”才是重灾区

boyanx1天前技术教程2

一开始写 Vue 的时候,谁不是觉得:“哇,组件好优雅!”三年后再回头一看,组件目录像垃圾堆,维护一处改三处,props 乱飞、事件满天飞,复用全靠 copy paste。于是我终于明白 —— 组件设计,才是 Vue 项目的重灾区 。


1. 抽组件 ≠ 拆文件夹

很多初学 Vue 的人对“组件化”的理解就是:“页面上出现重复的 UI?好,抽个组件。”

于是你会看到这样的组件:

<template>

<input :value="value" @input="$emit('update:value', $event.target.value)" />

</template>
 #技术分享

接着你又遇到需要加图标的输入框,于是复制一份:

<template>

<div class="icon-text-input">

<i class="icon" :class="icon" />

<input :value="value" @input="$emit('update:value', $event.target.value)" />

</div>

</template>

再后来你需要加验证、loading、tooltip……结果就变成了:

  • TextInput.vue
  • IconTextInput.vue
  • ValidatableInput.vue
  • LoadingInput.vue
  • FormInput.vue

组件爆炸式增长,但每一个都只是“刚好凑合”,共用不了。


2. 抽象失控:为了复用而复用,结果没人敢用

比如下面这个场景:

你封装了一个超级复杂的表格组件:

<CustomTable
  :columns="columns"
  :data="tableData"
  :show-expand="true"
  :enable-pagination="true"
  :custom-actions="['edit', 'delete']"
/>

你美其名曰“通用组件”,但别人拿去一用就发现:

  • 某个页面只要展示,不要操作按钮,配置了也没法删;
  • 有个页面需要自定义排序逻辑,你这边死写死;
  • 另一个页面用 element-plus 的样式,这边你自绘一套 UI;
  • 报错时控制台输出一大堆 warning,根本不知道哪来的。

最后大家的做法就是 —— 不用你这套“通用组件”,自己抄一份改改


3. 数据向下流、事件向上传:你真的理解 props 和 emit 吗?

Vue 的单向数据流原则说得很清楚:

父组件通过 props 向下传数据,子组件通过 emit 通知父组件。

但现实是:

  • props 传了 7 层,页面逻辑根本看不懂数据哪来的;
  • 子组件 emit 了两个 event,父组件又传回了回调函数;
  • 有时候干脆直接用 inject/providerefeventBus 偷偷打通通信。

举个例子:

<template>

<PageWrapper>

<ChildComponent :formData="form" @submit="handleSubmit" />

</PageWrapper>

</template>

<template>

<Form :model="formData" />

<button @click="$emit('submit', formData)">提交</button>

</template>

看上去还好?但当 ChildComponent 再包一层 FormWrapper 、再嵌套 InputList ,你就发现:

  • formData 根本不知道是哪个组件控制的
  • submit 被多层包装、debounce、防抖、节流、劫持
  • 你改一个按钮逻辑,要翻 4 个文件

4. 技术债爆炸的罪魁祸首:不敢删、不敢动

组件目录看似整齐,但大部分组件都有如下特征:

  • 有 10 个 props,3 个事件,但没人知道谁在用;
  • 注释写着“用于 A 页面”,实际上 B、C、D 页面也在引用;
  • 一个小改动能引发“蝴蝶效应”,整个系统发疯。

于是你只能选择 —— 拷贝再新建一个组件,给它加个 V2 后缀,然后老的你也不敢删。

项目后期的结构大概就是:

components/
├── Input.vue
├── InputV2.vue
├── InputWithTooltip.vue
├── InputWithValidation.vue
├── InputWithValidationV2.vue
└── ...

“为了让别人能维护我的代码,我决定不动它。”


5. 组件设计的核心,其实是 抽象能力

我用三年才悟到一个道理:

Vue 组件设计的难点,不是语法、也不是封装,而是你有没有 抽象问题的能力

举个例子:

你需要设计一个“搜索区域”组件,包含输入框 + 日期范围 + 搜索按钮。

新手写法:

<SearchHeader
  :keyword="keyword"
  :startDate="start"
  :endDate="end"
  @search="handleSearch"
/>

页面需求一改,换成了下拉框 + 单选框怎么办?又封一个组件?

更好的设计是 —— 提供 slots 插槽 + 作用域插槽

<template>

<div class="search-header">

<slot name="form" />

<button @click="$emit('search')">搜索</button>

</div>

</template>

<SearchHeader @search="search">

<template #form>

<el-input v-model="keyword" placeholder="请输入关键词" />

<el-date-picker v-model="range" type="daterange" />

</template>

</SearchHeader>

把结构交给组件,把行为交给页面。组件不掌控一切,而是协作。


6. 那么组件怎么设计才对?

我总结出 3 条简单但有效的建议:

1. 明确组件职责:UI?交互?逻辑?

  • UI 组件只关心展示,比如按钮、标签、卡片;
  • 交互组件只封装用户操作,比如输入框、选择器;
  • 逻辑组件封装业务规则,比如筛选区、分页器。

别让一个组件又画 UI 又写逻辑还请求接口。


2. 精简 props 和 emit,只暴露“必需”的接口

  • 一个组件 props 超过 6 个,要小心;
  • 如果事件名不具备业务语义(比如 click ),考虑抽象;
  • 不要用 ref 操作子组件的内部逻辑,那是反模式。

3. 使用 slots 替代“高度定制的 props 方案”

如果你发现你组件 props 变成这样:

<SuperButton
  :label="'提交'"
  :icon="'plus'"
  :iconPosition="'left'"
  :styleType="'primary'"
  :loading="true"
/>

那它该用 slot 了:

<SuperButton>

<template #icon><PlusIcon /></template>

提交 </SuperButton>

三年前我以为组件化是 Vue 最简单的部分,三年后我才意识到,它是最深、最难、最容易出坑的部分。

如果你也踩过以下这些坑:

  • 组件复用越写越复杂,别人都不敢用;
  • props 和事件像迷宫一样,维护成本极高;
  • UI 和逻辑耦合,改一点动全身;
  • 项目后期组件膨胀、技术债堆积如山;

别再让组件成为项目的“技术债”。你们也有遇到吗?

你可以继续看我的系列文章

  • 《我为什么觉得 React 正在逐渐失去吸引力?》
  • 《低代码是“未来”还是“骗局”?作为前端我被内耗到了》
  • 《为什么越来越多 Vue 项目用起了 UnoCSS?》
  • 《用好了 defineProps 才叫会用 Vue3,90% 的写法都错了》
  • 《为什么我放弃使用 Pinia?》
  • 《为什么我不再相信 Tailwind?三个月重构项目教会我的事》

相关文章

React v19 正式发布!(react15)

React 于 2024 年 12 月 06 日正式发布,为开发者带来了诸多令人兴奋的新特性和改进。下面对 React 19 版本中的核心更新和主要改进做一个快速介绍。React 19 核心更新Act...

阿东编程系列:APP使用用户名、密码和图片验证码进行登录

本期内容登录过程较为常见和简单,不再以视频的方式呈现(如果确实有需要,小伙伴可以发私信告知,到时候再录制视频)。这里给大家贴出代码和图片,方便小伙伴们去完成。1.APP端基于uni-app的登录页代...

消防安全知识答题活动小程序v4.1.0

消防安全知识答题活动小程序v4.1.0v4.1.01)支持多选题.wxml<checkbox-group class="checkbox-group" bindchange=&#...

OneCode设计器协议栈名词解析及标准概念适配

前言以下是基于 OneCode 技术体系,对标准协议内容进行适配性转换与解读(因 OneCode 有其自身生态规范,会结合其常见概念和流程调整表述,部分需结合 OneCode 实际框架灵活落地 ):一...

原生JS实现多标签页数据共享:优雅且高效

在现代Web开发中,多标签页之间的数据共享是一个常见的需求。无论是实现多页面之间的实时通信,还是同步用户状态,都能极大地提升用户体验。然而,传统的实现方式往往需要借助复杂的框架或第三方库。今天,我们将...

手机网站常见问题总结(手机网站访问异常怎么解决)

一、h5网站input 设置为type=number的问题h5网页input 的type设置为number一般会产生三个问题,一个问题是maxlength属性不好用了。另外一个是form提交的时候,默...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。