element-plus type.text is about to be deprecated in version 3.0.0, please use link instead.这条警告,我在升级老后台项目时已经看了不下几十遍。可能你也在控制台见过它,只是没当回事——毕竟项目还能跑,业务没异常,顶多日志里多一条黄色“废弃提示”,有人干脆在vue.config.js里把 console 过滤掉图个清净。但如果你把 element-plus 锁在 2.x,这条提示确实可以一直忽略;可一旦团队决定升到 3.x,它就不再是 warning,而是直接变报错——type="text"会从组件库中彻底消失。
这篇文章适合两种人看:一种是正被老项目里几百处type="text"困扰、准备做 element-plus 大版本升级的前端工程师;另一种是刚开始用 element-plus,从网上复制了旧代码却总看到 deprecation 告警的新手。下面我会把这次废弃完整拆开:官方为什么砍掉它、迁移前要做哪些盘点、具体怎么替换、会遇到哪些坑,以及怎么避免下次再被废弃 API 打个措手不及。
1. 这行警告到底在说什么
1.1 从一个“旧时代的流行写法”说起
<el-button type="text">这个写法,经历过 Element UI 时代的人都不会陌生。在 element-ui 里,文字按钮几乎是后台管理系统的标配:表格操作列放“编辑”“删除”,权限按钮组放“新增”“导出”,它都能用最轻量的视觉形式承载。当年很多后台模板都是这么写的,以至于后来大家复制代码时根本不会多想,type="text"就和el-table、el-pagination一样自然。
element-plus 刚出的时候,为了兼容老用户的使用习惯,也保留了这个 API。但组件库不是一成不变的,在 2.x 的某个 minor 版本(我记得大概 2.2 开始),官方在开发模式下加了这条 Warning,提醒开发者:“这种用法会在 3.0.0 正式删除,请改用el-link。” 这其实是一个非常标准的“弃用预告”流程:先通过 console 警告给出一整个大版本的过渡期,让用户知道未来要改,而不是在 3.0 里一声不吭直接删。
不过这里有个容易踩的认知误区:很多人以为type="text"和el-link只是名字不同、换个标签名就行。实际上两者在语义层面就有本质区别,后面我会详细对比。如果你只是机械地把type="text"替换成<el-link>,大概率能跑,但交互细节、表单提交行为、整体样式都会出现不同程度的“违和感”,视觉回归和功能 bug 往往就是这么冒出来的。
1.2 官方执意砍掉 type="text" 的理由
先说结论:官方不是闲得慌,而是这个 API 的存在让组件库的语义边界变得很混乱。
<el-button>本质是一个 button 元素,语义是“触发一个动作”。它天然承担表单提交、弹窗确认、加载状态这些和交互动作强相关的职责。而type="text"这种变体,只是把按钮的视觉改成了“像文字一样”,但它依然是 button,依然可以提交表单、依然有按钮的内边距。问题就出在这里:当你在一个操作列里放一排“文字按钮”时,用户看到的是“链接”,但它实际上不跳转、没有 href,只是长得像链接的按钮。视觉和语义的割裂,对无障碍访问(a11y)也不友好——屏幕阅读器会把它读作按钮而非链接,键盘交互、焦点样式都和真正的链接不同。
从维护角度讲,type="text"也让组件内部的样式逻辑变得很拧巴。element-plus 的按钮类型体系是 default / primary / success / info / warning / danger,每一种对应一个语义颜色;text混在里面,既不是颜色语义,也不是 size 语义,而是一种“形态语义”,和其他 type 完全不在一个维度上。为了支持它,组件里要额外处理边框、背景、hover、focus 多套分支,平白增加复杂度。
总之一句话:el-link已经能覆盖 99% 的文字按钮场景,官方干脆在 3.0 大版本做一次 breaking change,把历史包袱卸掉。这不只是 element-plus 的做法,很多成熟组件库都会在大版本里收敛 API——把重复能力合并成一套,把模糊语义归一化。理解了这一点,你就能明白为什么迁移不能只靠“无脑替换”。
2. 迁移前先摸清两者的能力边界
2.1 el-button type="text" 与 el-link 的能力对比
动手之前,我强烈建议你先做一次能力盘点,搞清楚两个组件各自的边界。我把最关键的对比列一张表,你在改造时照着看会清晰很多。
| 能力维度 | el-button type="text" | el-link |
|---|---|---|
| 渲染标签 | <button> | <a> |
| 语义 | 触发动作 | 跳转/链接 |
| 点击事件 | 原生 click | 原生 click,但要注意 anchor 默认行为 |
| href 属性 | 不支持 | 支持 |
| target(新窗口) | 需要自己写 window.open | 原生支持 target="_blank" |
| disabled | 支持 | 支持 |
| loading | 支持 | 不支持 |
| size 属性 | 支持 | 不支持 |
| 默认内边距 | 有,按钮自带 | 无,纯文本 |
| hover 效果 | 背景色变化 | 显示下划线 |
| 表单内提交能力 | 可以触发 form submit | 不可以,需要手动处理 |
这张表里最需要关注的有两行:loading 不支持和表单内提交能力。
el-button type="text"支持 loading,这在一些异步操作按钮里很常见,比如“保存中…”“提交中…”。改成el-link后没有 loading 属性,你只能自己用一个局部v-loading指令,或者干脆用一个动态 class 切换文案和 icon。这个改动不算复杂,但很容易被忽略。
表单提交就更隐蔽了。假设你原来在某个el-form里放了一个type="text"的“查询”按钮,它天然是 button 元素,点击后会触发表单的 submit 事件。改成el-link后它是<a>标签,根本不会提交表单。如果你没有意识到这一点,功能会在毫无报错的情况下悄悄失效,这是最让人头疼的一种 bug。
2.2 动手前的全局代码盘点
在批量替换之前,先花半小时做一次全局代码盘点,会大大降低后续的返工成本。我用得最多的方法就是 grep,把项目里所有type="text"先捞出来,按使用场景分类。
grep -rn "type=\"text\"" src --include="*.vue"如果你是 TS + Vue 项目,可能还有 tsx 或 render 函数里的写法,也要一并扫:
grep -rn "type: ['\"]text['\"]" src --include="*.tsx" grep -rn "type: ['\"]text['\"]" src --include="*.ts"拿到结果后,先不要急着改,把每一处使用场景归类到下面几个典型类型里:
- 表格操作列:最常见,比如“编辑 / 删除 / 详情”,基本是纯 click 触发,迁移最安全。
- 表单内的按钮:比如“查询 / 提交 / 重置”,这类要特别注意表单提交逻辑,不能只换标签。
- 页面里的文字入口:比如“查看更多”“全部订单”,这类本质是导航跳转,换成
<el-link>反而更合适。 - 权限按钮组:通常是根据角色动态渲染的一组操作项,可能还混用了 icon、disabled、v-if,需要逐项核对。
做完分类后,你心里就有数了:哪些是“无脑替换”、哪些需要“特殊处理”。批量迁移最忌讳的就是一股脑全替换完,结果打开页面才发现功能悄悄变了。
3. 实操迁移:三步把 type="text" 换成 link
3.1 第一步:最简替换,处理可以直接换的部分
很多场景其实可以直接替换,尤其是表格操作列里那种纯点击触发、没有任何表单语义的“编辑”“查看”按钮。我们拿一个最典型的例子来说。
迁移前:
<template> <el-table-column label="操作"> <template #default="{ row }"> <el-button type="text" @click="handleEdit(row)">编辑</el-button> <el-button type="text" @click="handleDelete(row)">删除</el-button> </template> </el-table-column> </template>迁移后:
<template> <el-table-column label="操作"> <template #default="{ row }"> <el-link type="primary" @click="handleEdit(row)">编辑</el-link> <el-link type="danger" @click="handleDelete(row)">删除</el-link> </template> </el-table-column> </template>表面看只改了标签,但有两个细节需要马上确认。
第一,颜色语义变了。el-button type="text"默认文字颜色是 primary 蓝,但很多项目在主题里自定义过--el-color-primary,所以实际渲染出来的蓝可能和你想象中不一样。而el-link的type="primary"会走同一套主色变量,一般问题不大;如果你原来想要的是灰黑色文字,可以显式给el-link加type="info",或者用一个自定义 class 覆盖。
第二,间距没了。按钮自带水平内边距,所以在表格操作列里出现“编辑 删除”时,两个按钮之间天然有间距;el-link是纯文本,多个链接挤在一起,视觉上会粘成一团。这里我建议给操作列统一加一个间距类:
<template> <div class="table-action-cell"> <el-link type="primary" @click="handleEdit(row)">编辑</el-link> <el-link type="danger" @click="handleDelete(row)">删除</el-link> </div> </template> <style scoped> .table-action-cell .el-link + .el-link { margin-left: 12px; } </style>这个小小的间距处理,经常是项目改完后“看起来怪怪的”的元凶。代码层面明明都换了,但视觉上总觉得操作列没有以前整齐,其实就是少了间距和 hover 反馈。
3.2 第二步:处理事件、加载状态与表单提交
第二类场景需要多动一步,主要是带disabled、loading、以及位于表单内的按钮。
先看disabled,这个可以直接映射:
<template> <el-link type="primary" :disabled="!hasPermission" @click="handleExport" > 导出数据 </el-link> </template>el-link在disabled状态下会失去点击反应,并且应用降透明度的禁用样式,基本可以等效替代。
再看loading,这个就比较麻烦了。el-button自带 loading 旋转圈,换成el-link之后没有对应属性。我当时迁移时采用的方式是局部v-loading包裹,或者在el-link内部手动渲染一个 Loading 图标,再配合loading状态禁用点击。
<template> <el-link type="primary" :disabled="loading" @click="handleSave" > <el-icon v-if="loading" class="is-loading" :size="14" style="vertical-align: middle"> <Loading /> </el-icon> <span style="vertical-align: middle">{{ loading ? '保存中...' : '保存' }}</span> </el-link> </template> <script setup> import { Loading } from '@element-plus/icons-vue' </script>样式上给图标加一个is-loadingclass,让它旋转起来。这样视觉交互能基本还原旧按钮的 loading 体验,耗时也不会太长。
最后是表单提交场景,这是最容易出功能 bug 的地方。假设你原来在查询表单里放了一个文字按钮,点击后触发表单提交并执行查询:
<el-form :model="queryForm" @submit.prevent="handleSearch"> <el-button type="text" native-type="submit">查询</el-button> </el-form>直接替换成el-link后,handleSearch不会被触发。正确做法是去掉对表单 submit 的依赖,改成显式调用表单提交逻辑:
<el-form ref="searchFormRef" :model="queryForm" @submit.prevent="handleSearch"> <el-link type="primary" @click.prevent="handleSearch">查询</el-link> </el-form>@click.prevent是为了阻止<a>标签的默认跳转行为,因为el-link即使不传href,有些情况下浏览器也会因为它是可点击元素产生多余行为,加一层prevent更稳妥。如果你的查询逻辑依赖表单校验,那就在handleSearch里先拿searchFormRef调用validate,校验通过后再发起请求。
3.3 第三步:用一套自定义样式还原旧观感
替换完成后,大概率会出现一次视觉回归:旧文字按钮有内边距、hover 时背景会淡淡变灰,而el-link默认是纯文本、hover 出现下划线。如果你们的 UI 设计师对细节敏感,这一步就得认真处理。
我当时的做法是封装了一个.legacy-text-btn类,在替换后的el-link上统一加上,用一套 CSS 把旧文字按钮的观感还原回来:
.legacy-text-btn.el-link { padding: 0 4px; font-size: 14px; line-height: 22px; transition: color 0.2s, background-color 0.2s; border-radius: 4px; } .legacy-text-btn.el-link:hover { color: var(--el-color-primary); background-color: var(--el-color-primary-light-9); text-decoration: none; } .legacy-text-btn.el-link.is-disabled { color: var(--el-text-color-disabled); background-color: transparent; }这套样式主要做三件事:恢复左右内边距,让多个链接并排时不用额外加间距;恢复 hover 时的浅色背景,让鼠标悬停反馈接近旧文字按钮;禁用状态下保持旧按钮的淡灰色,而不是沿用el-link默认的禁用样式。实际应用时,你们可以根据项目主题变量微调颜色值。
如果你的项目里这种旧按钮用在很多地方,我建议不要一个一个手写 class,而是在公共样式文件里全局定义,然后替换时顺手加上。这样以后想调整样式,只需要改一个文件。
4. 迁移过程中常见的坑和排查
4.1 “明明改了,为什么还报废弃警告?”
这应该是迁移结束后最容易碰到的问题。代码里已经找不到任何type="text"了,但控制台仍不断打印 deprecated 提示。排查顺序基本是下面三步。
第一,确认依赖树里是否还有旧版本的 element-plus。有些项目同时存在顶层依赖和嵌套依赖,或者是 pnpm 的 hoist 策略导致node_modules里锁了一份旧版本。建议先跑一下:
npm ls element-plus如果发现项目中实际解析到的版本不是预期版本,优先清理锁文件重新安装。
第二,检查第三方组件库或内部业务组件是否还在用旧 API。我遇到过这种情况:自己页面的代码全改完了,但某个自研表格组件内部封装了el-button type="text",只要页面使用了那个组件,警告就会一直出现。解决方案是全局再搜一次type="text",把范围扩大到node_modules内的业务组件包,或者让组件库负责人同步升级。
第三,确认构建产物被正确清理。有时候是开发服务器或 CI 缓存了旧的编译产物。尤其是用了 Vite 的项目,node_modules/.vite缓存会保留旧的模块信息,跑一遍vite --force或者删掉缓存目录再启动,警告往往会消失。
4.2 表格操作列的视觉回归比想象中更多
在表格操作列里,el-button type="text"原本是有固定行高和内边距的,所以表格行的高度、操作列的宽度都形成了微妙的平衡。替换成el-link后,最直接的影响就是操作列宽度变窄、操作项之间的间距消失。
排查时不要只盯着页面看,建议打开浏览器 DevTools 对比一下操作列容器的实际宽度,以及相邻操作项之间的空隙。如果出现错位,顺手处理两个问题:给外层容器加flex布局并设置align-items: center;给操作项设置统一样式类,用margin-right控制间距。
另外还有一个比较容易漏的地方:旧文字按钮在disabled状态下,鼠标样式是not-allowed;el-link默认也是,但如果你自定义了.legacy-text-btn的cursor样式,需要确认没把它覆盖掉。
4.3 render 函数和 tsx 中的迁移写法
如果你的项目里有动态渲染的列配置,或者用h函数生成操作按钮,替换写法也要跟着调整。我见过不少项目在columns配置里用render函数生成操作列,这里的替换最容易踩类型坑。
迁移前:
import { h } from 'vue' import { ElButton } from 'element-plus' const renderAction = (row) => { return h( ElButton, { type: 'text', onClick: () => handleDelete(row), }, () => '删除' ) }迁移后:
import { h } from 'vue' import { ElLink } from 'element-plus' const renderAction = (row) => { return h( ElLink, { type: 'danger', onClick: () => handleDelete(row), }, () => '删除' ) }这里需要注意两点。第一,ElLink的类型定义中,onClick接收的是MouseEvent,如果你原来的处理函数里有event参数、并且用到了event.preventDefault(),要确认类型仍然匹配。第二,如果原来的 render 函数里给ElButton传了size属性,迁移后ElLink原生不支持size,需要在样式中补齐,否则出现不明显的尺寸差异。
4.4 常见问题速查表
我把迁移中高频出现的问题整理成一张速查表,方便你按图索骥。
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 控制台仍报 deprecated 警告 | 依赖树有旧版本 / 业务组件内部还在用 | npm ls element-plus排查,外部包同步升级 |
| 点击查询/提交按钮后表单不提交 | el-link渲染为<a>,不是 submit 按钮 | 改用@click.prevent显式调用表单校验和提交 |
| 操作列多个文字按钮挤在一起 | el-link没有按钮内边距 | 统一加间距类,如margin-left: 12px |
| 按钮 loading 效果消失 | el-link不支持 loading 属性 | 用局部v-loading或自定义 icon 旋转 |
| 旧 hover 背景色不见了 | 两种组件 hover 机制不同 | 追加.legacy-text-btn自定义样式 |
| disabled 后仍可点击 | 检查是否传了disabled或样式被覆盖 | 确认:disabled绑定,设置cursor: not-allowed |
| render 函数中 TS 类型报错 | ElLink事件类型与ElButton不同 | 调整onClick参数类型或整个事件定义 |
5. 从一次废弃看组件库升级策略
5.1 别再把 deprecation warning 当噪音
这次迁移给我最大的感触,不是替换了一百多处代码,而是不要忽略 deprecation warning。
很多前端团队习惯把控制台警告“绿色化”成日常,看到黄色提示直接无视。但严格来说,这类警告是组件库给你留的缓冲期:它在 2.x 时代就明确告诉你了,这个 API 未来会删,现在还有时间改。等到 3.0 真的发布,断崖式报错会直接把项目升级计划卡死,到时候再慌慌张张做迁移,成本和风险都翻倍。
建议把仓库里的 deprecation warning 当成一张 TODO 清单来管理。比如这次迁移前,可以统计一下警告出现次数、涉及页面、使用场景,形成一张迁移任务表。这样不仅能顺利升级 element-plus,以后遇到其他库的废弃告警也可以套用同一个思路。
这里还联想到很多其他技术栈里相似的场景,比如某些浏览器原生 API 的废弃、Node 版本升级时各种 npm 包告警、新版本构建工具不再兼容旧配置项等等。技术生态的演进本质上就是一段段从 deprecated 到 breaking change 的过程,前端对这部分尤其敏感。我们能做的不是抱怨“又来了”,而是建立一套应对废弃 API 的例行机制。
5.2 一套可复制的升级前检查清单
这次迁移结束后,我顺手总结了一套组件库大版本升级的安全流程,分享出来供你参考。
第一步,先冻结版本。在准备升级前,把 element-plus 版本锁定在当前使用的小版本,避免升级过程中依赖自动漂移带来额外变量。
第二步,全局扫描废弃 API。不要只搜type="text",把项目里所有可能引发 warning 的 API 都扫一遍。像是 component name 的废弃、插槽改名、方法替换,凡是控制台出现过提示的都记录在案。
第三步,按场景分批修改。把改动分成“纯替换”和“需重构”两类。纯替换的可以用 codemod 或正则批量改,需重构的留给人工处理。我当时把表单内的按钮、带 loading 的按钮、render 函数里的按钮挑出来优先处理,剩下的表格操作列节点最后统一批量改,效率高很多。
第四步,用视觉回归作为验收标准。改完代码只是第一步,页面视觉效果、交互细节都要对照旧版验收。有条件的话,在改造前后分别截图做对比,或者用 Playwright 这类工具跑一遍核心路径的截图 diff,能快速发现间距、颜色、hover 效果的变化。
第五步,灰度上线。建议先用一个低流量页面或模块试运行,观察有没有用户反馈交互异常,确认稳定后再全量替换。虽然type="text"到el-link是官方推荐的路径,但每个项目的主题定制、全局样式覆盖度不同,灰测能兜住最后一层风险。
5.3 迁移完,不妨回头审视其他历史代码
type="text"的迁移是一个很好的契机,可以顺带把项目的组件使用习惯梳理一遍。很多老项目里不仅有type="text",还有一堆早就该淘汰的旧写法,比如:
- 手写
window.open而不是使用统一的跳转封装; - 在表格操作列里面写一整串重复的
el-button,没有抽象成列配置; - 把
el-link的活交给了el-button去做,导致所有“看起来像链接”的交互都堆在按钮组件上。
借着这次迁移,我会建议大家在操作列组件层面做一层统一封装,比如把“编辑 / 删除 / 详情”这些操作项抽成一个TableActions组件。这样即使以后某个操作项要加禁用、加权限、加 loading,都只需要改一处。同时也把el-link真正用在该用的地方:凡是带href跳转的,一律用el-link;凡是纯触发行为的,继续用el-button。语义清晰了,维护成本自然就降下来了。
最后再分享一个小技巧:如果你现在还在 2.x 版本、短期无法升 3.0,不用急着把type="text"全部改掉,但务必在升级前完成迁移。反过来,如果你刚开始一个新项目,就直接使用el-link吧,别再去复制过去那些老代码了。旧写法的“兼容期”不是无限期的,越早做功能性替换,后面的升级成本就越低。