组件封装,为什么要二次封装组件,Elementplus如何改样式
面试先给结论
如果面试官问:
- 为什么要二次封装组件
- Element Plus 如何改样式
- 组件封装在项目里怎么做
你可以围绕这三点答:
二次封装组件的核心目的,是把业务里重复使用的 UI 和逻辑做统一抽象,提升复用性、规范性和可维护性。
像 Element Plus 这种组件库,能快速搭页面,但它提供的是通用能力,真正落地到项目里,通常还需要再包一层,统一默认行为、交互、样式、权限、埋点和业务规则。
样式改造上,一般会根据场景选择class覆盖、CSS 变量、:deep()、SCSS 主题定制等方式。
这段就是比较成熟的面试回答方向。
一、为什么要二次封装组件
这个问题本质上不是“会不会封装”,而是问你有没有工程化和组件设计思维。
1. 统一项目风格和交互
组件库本身是通用的,但项目往往有自己的规范,比如:
- 按钮默认尺寸统一
- 弹窗标题、按钮文案统一
- 表单间距统一
- 表格空状态、加载态统一
- 输入框字数限制、校验提示统一
如果每个页面都单独改:
- 代码重复
- 风格不一致
- 后期改动成本高
所以会二次封装,比如:
BaseButtonBaseDialogBaseTableSearchForm
面试表达
二次封装首先是为了统一 UI 风格和交互规范,避免业务页面各自写一套,导致项目体验不一致。
2. 提高复用性,减少重复代码
很多业务场景会反复出现,比如:
- 列表页查询表单
- 分页表格
- 弹窗表单
- 上传组件
- 权限按钮
- 远程搜索下拉框
如果直接用 Element Plus 原始组件,页面上会写很多重复逻辑,比如:
- loading 处理
- 分页参数处理
- 空数据提示
- 请求节流防抖
- 关闭弹窗后的重置逻辑
二次封装后,页面层只关心业务数据,不用每次重写。
面试表达
二次封装可以把重复的 UI 结构和业务逻辑抽到组件内部,减少样板代码,提高开发效率。
3. 屏蔽底层实现,降低替换成本
比如项目现在用的是 Element Plus,以后如果:
- 要升级版本
- 某个组件 API 变了
- 要局部替换成别的组件库
- 要做主题升级
如果业务页面全都直接写el-dialog、el-table,改动会很大。
如果中间有一层自定义组件,比如:
MyDialogMyTable
那外部业务代码基本不用动,只要调整内部实现。
面试表达
二次封装还有一个价值是隔离底层组件库,对业务层做一层抽象,后续升级或替换时成本更低。
4. 注入业务能力
这个很加分。
组件库通常只解决通用 UI,不会帮你处理项目里的业务能力,比如:
- 权限控制
- 埋点
- 国际化
- 统一错误处理
- 默认参数
- 接口联动
- 表单校验策略
这时就要通过二次封装把这些能力整合进去。
例子
比如二次封装一个按钮:
- 自动做权限判断
- 自动加 loading
- 自动防重复点击
- 自动埋点
面试表达
组件库更多解决通用渲染问题,而二次封装可以把权限、埋点、默认行为、接口联动这类业务能力沉淀进去。
二、什么场景下会二次封装
这个问题面试官也常追问。
常见封装对象
1. 基础组件
比如:
- 按钮
- 输入框
- 弹窗
- 抽屉
- 表格
- 分页
- 表单项
2. 业务组件
比如:
- 搜索表单
SearchForm - 通用表格
ProTable - 上传组件
UploadFile - 地址选择器
- 组织树选择器
- 用户选择弹窗
3. 组合型组件
比如:
Table + Pagination + ToolbarDialog + Form + FooterSelect + Remote Search
三、二次封装要解决哪些问题
面试里不要只说“方便复用”,最好再说得更细一点。
1. 默认值统一
例如:
- 所有按钮默认
type="primary" - 所有表格默认有 loading、stripe、border
- 所有 dialog 默认点击遮罩不关闭
2. 对外 API 更简单
Element Plus 的某些组件参数比较多,业务方其实只用其中一部分。
封装后可以:
- 简化 props
- 暴露更符合业务语义的参数
- 提供统一事件
比如把复杂的el-table包装成:
<ProTable :columns="columns" :data="list" :loading="loading" />而不是每个页面都写大量模板。
3. 统一状态处理
例如:
- loading
- empty
- disabled
- error
- reset
- submit
4. 统一样式和主题
四、Element Plus 如何改样式
这个是非常高频的追问。
你要回答:
Element Plus 改样式通常有几种方式,具体看是改单个组件、局部页面,还是全局主题。
1. 通过自定义 class 覆盖样式
这是最常见的方式。
示例
<el-button class="my-btn">提交</el-button>.my-btn { border-radius: 8px; }适用场景
- 只改当前页面某个组件
- 改动不大
- 局部覆盖
2. 在 Vue SFC 中用:deep()修改内部样式
因为很多 Element Plus 组件内部结构有自己的类名,如果你用了scoped,直接写可能不生效,这时候要用深度选择器。
示例
<template> <el-dialog class="custom-dialog" /> </template> <style scoped> .custom-dialog :deep(.el-dialog__header) { padding: 12px 16px; } .custom-dialog :deep(.el-dialog__body) { padding: 20px; } </style>面试表达
如果是在单文件组件里并且用了
scoped,我一般会通过:deep()去覆盖 Element Plus 内部类名。
3. 使用组件提供的 CSS 变量
Element Plus 很多样式是基于 CSS 变量实现的,这是比较推荐的方式。
示例
:root { --el-color-primary: #1677ff; --el-border-radius-base: 6px; }或者局部改:
<div class="custom-theme"> <el-button>按钮</el-button> </div> <style> .custom-theme { --el-color-primary: #ff4d4f; } </style>优点
- 更稳定
- 不依赖内部 DOM 结构
- 升级时风险更小
面试表达
如果 Element Plus 暴露了 CSS 变量,我会优先改变量,因为这种方式更稳定,不容易受内部结构变更影响。
4. 通过主题定制 / SCSS 变量覆盖
如果是全局主题定制,比如:
- 主色
- 成功色
- 警告色
- 圆角
- 字体
- 间距
可以通过 Element Plus 的主题变量来统一配置。
这个适合:
- 企业后台统一品牌色
- 全站统一视觉风格
5. 直接覆盖官方类名
比如:
.el-table th.el-table__cell { background-color: #f5f7fa; }这种方式能用,但要注意:
- 容易污染全局
- 依赖官方内部类名
- 升级后可能失效
所以面试中要说
直接覆盖官方类名我会谨慎使用,优先加容器类做作用域限制,避免影响全局。
五、Element Plus 改样式时要注意什么
这部分很加分。
1. 优先级问题
Element Plus 自己的样式优先级可能比较高,必要时:
- 增加选择器层级
- 用局部容器包裹
- 少量场景下才用
!important
面试表达
我一般不推荐大量用
!important,会优先通过更明确的作用域和层级覆盖样式。
2. 避免全局污染
不要直接在全局无脑写:
.el-button { ... }这样可能影响整个项目。
更好的方式:
.page-user .el-button { ... }或者封装成自定义组件。
3. 尽量不要过度依赖内部结构
比如某些组件的 DOM 结构升级后会变,直接写太深的选择器风险大。
所以优先级建议一般是:
CSS 变量 > 官方暴露的 class / slot / props >
:deep()覆盖 > 直接写内部结构
六、面试中怎么回答“为什么要二次封装 Element Plus 组件”
这个很常见,你可以直接说:
Element Plus 提供的是通用能力,但在实际业务里,很多组件都会有统一的默认行为、样式规范和业务逻辑,比如表格的分页、空状态、加载态,弹窗的关闭逻辑,按钮的权限控制和防重复提交等。
所以我通常会在 Element Plus 的基础上再封装一层,把通用配置、常见交互和业务能力沉淀下来。这样做有几个好处:一是提升复用性,减少重复代码;二是统一项目风格和交互;三是对底层组件库做隔离,后续升级或替换成本更低。
这段非常适合面试直接说。
七、如果让你举例,你怎么说
你可以举一个最稳的例子:表格组件二次封装
示例:封装ProTable
为什么封装
因为项目里的列表页几乎都有这些重复内容:
- 查询条件
- 表格列配置
- loading
- 分页
- 空状态
- 操作列按钮权限
- 表格高度自适应
封装后
外部只传:
columnsrequestparams
内部处理:
- 发请求
- loading
- 分页同步
- 表格渲染
- 空状态
- 插槽扩展
面试表达
比如我封装过基于 Element Plus 的表格组件,把分页、请求、loading、空状态、工具栏和列渲染能力都统一进去,页面层只需要关心 columns 和查询参数,能明显减少样板代码。
八、如果问“封装组件时你会注意什么”
这个问题非常像面试深挖。
你可以答这几点:
1. API 要稳定、清晰
- props 不要设计得太乱
- 事件命名统一
- 默认值合理
2. 可扩展性
- 支持插槽
- 支持透传 attrs
- 支持事件透传
- 不要封得太死
这点很重要
二次封装不是把组件封死,而是在统一能力的基础上保留灵活扩展能力。
3. 样式隔离
- 避免污染全局
- 提供 class 前缀
- 支持主题化
4. 文档和使用规范
- 组件怎么传参
- 支持哪些插槽
- 事件含义是什么
- 边界场景怎么处理
九、面试标准回答模板
你可以直接背这段:
在项目里我会对 Element Plus 这类组件库做二次封装,因为组件库本身提供的是通用 UI 能力,但实际业务往往需要统一默认行为、交互规范和样式风格。
比如按钮、弹窗、表格、表单这些组件,在不同页面里会反复出现,如果每次都直接使用原始组件,就容易出现代码重复、风格不统一、维护成本高的问题。
所以我通常会在组件库基础上再包一层,把通用配置、分页逻辑、loading、空状态、权限控制、防重复提交、埋点等能力沉淀进去,提高复用性和可维护性。
至于 Element Plus 样式修改,我一般会根据场景选择合适方式:局部样式用自定义 class 覆盖;在scoped场景下用:deep()修改内部类名;如果组件支持 CSS 变量,我会优先改变量;如果是全局主题,就通过主题变量统一定制。
总体思路是:功能上做抽象,样式上做分层,既保证统一性,也保留扩展性。
十、30 秒短答版
二次封装组件主要是为了统一项目风格、减少重复代码、沉淀业务能力,并且隔离底层组件库,降低后续维护和替换成本。
像 Element Plus,我一般会对表格、弹窗、表单、按钮这些高频组件再封装一层。
改样式时,局部我会用 class 覆盖或:deep(),全局主题优先用 CSS 变量或主题变量,这样比直接改内部类名更稳定。
十一、一句话总结
二次封装不是重复造轮子,而是把通用组件能力变成“适合当前项目”的能力。