从 Vue 2 搬家到 Vue 3,就像搬进新房子,发现原来的家具尺寸不对。尤其是你满屋子都是 Element UI 的老朋友,现在要迁到 Element Plus 的新家,有些家具被扔了,有些改了名,有些用起来跟以前完全不是一个手感。这篇文章不绕弯子,直接帮你把迁移路上那些坑一个个填平。
一、升级前,先做一次“家庭大扫除”
1.1 为什么升级不是“换个包名”那么轻松
Element UI 当年是给 Vue 2 量身定做的,而 Element Plus 是在 Vue 3 的骨架上重新长出来的。Vue 3 的底层响应式从 Object.defineProperty 换成了 Proxy,组件的通信方式也变了,比如 v-model 的机制调整、插槽语法更新、全局 API 全部转移。这些变化意味着 Element Plus 不可能只是把老代码原样拿过来,它必须重新设计一部分组件 API。
所以你在老项目里写得欢快的 visible.sync,到了 Vue 3 里已经不认识你了。你随手写的 type="text",人家官方也懒得支持了。说白了,升级这事儿,不是换个 npm 包那么简单,你得把“旧生活习惯”改一改。
1.2 动手前先盘点你家里有什么
在改代码之前,我建议你把项目里跟 Element UI 有关的地方都翻出来。打开 package.json,看看 element-ui 的版本号,再到代码里全局搜索这些关键词:
# 技术栈:Vue 3 + Element Plus
# 搜索老项目里常见的 Element UI 用法
grep -r "visible.sync" src/
grep -r "type=\"text\"" src/
grep -r "el-icon-" src/
grep -r "prefix-icon" src/
搜出来的地方,就是你要缝补的破洞。如果有测试用例,先把测试跑一遍,记录报错;没有测试,就手动把主要页面点一遍。这个过程看似费时间,但能让你后面少熬几个夜。
二、组件层面的“拆迁通知”
2.1 按钮里的“text”型演员下岗了
以前我们特别喜欢写一种“文字按钮”,就是那种长得很像超链接、点一下才能看出是按钮的玩意儿。在 Element UI 里这样写:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移前:Element UI -->
<el-button type="text">删除</el-button>
Element Plus 一看,type 应该是 primary、success 这种正经颜色,你弄个 text 算怎么回事?所以它直接把这个用法废弃了,选了一个新属性叫 link,意思就是“让按钮看起来像链接”。迁移后变成:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移后:Element Plus -->
<el-button link>删除</el-button>
如果你忘了改,这个按钮会变成普通按钮的样子,带着边框和背景,界面一下子就变得乱糟糟的。这种“不报错但变丑”的坑最让人难受。
2.2 图标从“字体”整容成“组件”
Element UI 的图标用 <i class="el-icon-edit"> 这种字体图标,简单粗暴。Element Plus 换了思路,把图标做成了 SVG 组件。你需要先安装图标包:
# 技术栈:Vue 3 + Element Plus
npm install @element-plus/icons-vue
然后代码里这样用:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移后:Element Plus 图标用法 -->
<template>
<el-icon><Edit /></el-icon>
</template>
<script setup>
import { Edit } from '@element-plus/icons-vue'
</script>
注意:图标名字从 el-icon-edit 变成了 Edit,大小写也得跟着变。如果你项目里用了二三十个图标,建议去官方图标列表里查一遍新名字。否则页面上就是一个小方框,连个报错提示都没有,非常容易漏。
2.3 消息弹窗不再“挂”在 Vue 上
以前用 Element UI 的消息提示,都是 this.$message.success('搞定') 或者 this.$confirm('确定?')。这依赖于 Vue.prototype 上挂了一个全局方法。到了 Vue 3,组件实例上可没有那么多魔法属性了。Element Plus 把方法变成了模块导出,你得自己引入:
// 技术栈:Vue 3 + Element Plus
import { ElMessage, ElMessageBox } from 'element-plus'
// 以前:this.$message.success('保存成功')
ElMessage.success('保存成功')
// 以前:this.$confirm('确定删除吗?').then(() => {})
ElMessageBox.confirm('确定删除吗?').then(() => {
console.log('确定删除')
})
看起来变化不大,但所有调用处都要改。如果项目里直接挂着几十个 this.$message,你会体会到什么叫“同样的话重复说”。
三、属性变更:同一个名字,不同的性格
3.1 弹窗的 visible.sync 变成了 v-model
这是最典型的坑。以前 Element UI 的弹窗、气泡、工具提示,都要用 :visible.sync 来控制显隐。Vue 3 的 v-model 机制升级了,Element Plus 干脆把这类组件一律统一成 v-model:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移前:Element UI -->
<el-dialog :visible.sync="dialogVisible">
<p>我是一段提示</p>
</el-dialog>
<!-- 迁移后:Element Plus -->
<el-dialog v-model="dialogVisible">
<p>我是一段提示</p>
</el-dialog>
顺便说一下原理,Vue 3 里组件的 v-model 默认接收一个叫 modelValue 的 prop,然后触发 update:modelValue 事件。Element Plus 的 el-dialog 内部正是这样实现的。所以你可以直接写 v-model,不用再拆成 :visible 和 @update:visible。
如果你不改,弹窗根本打不开,而且控制台会提示你“请使用 v-model”。但生活中有很多人不看控制台,只盯着空页面发呆。
3.2 尺寸的“话语体系”变了
Element UI 里按钮、输入框的尺寸有 medium、small、mini。Element Plus 把 medium 删了,默认就是中等大小,你写了反而多余;mini 也改名了,变成了 small。看代码:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移前:Element UI -->
<el-button size="medium">中等按钮</el-button>
<el-button size="mini">迷你按钮</el-button>
<!-- 迁移后:Element Plus -->
<el-button size="default">中等按钮</el-button> <!-- 其实不写也行 -->
<el-button size="small">迷你按钮</el-button>
这种改名字的坑最烦,因为不报错,只是视觉上差了那么几个像素。你不仔细看,根本发现不了按钮比其他地方大了一圈。
3.3 复选框和单选框的“身份分离”
以前咱们写复选框,label 既当显示文字又当存储值:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移前:Element UI -->
<el-checkbox v-model="hobbies" label="篮球"></el-checkbox>
这里的 label="篮球" 把“篮球”既显示在界面上,又作为选中的值存进 hobbies。Element Plus 觉得这不符合“数据与展示分离”的原则,于是把角色的账算清楚了:label 只管显示文字,新增的 value 才是真正要提交的数据。
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移后:Element Plus -->
<el-checkbox v-model="hobbies" label="篮球" value="basketball"></el-checkbox>
如果你不写 value,默认值还是 label 的文字,老代码能用。但一旦遇到“显示中文,提交英文”这种需求,你就得回来补这个写法。单选框 el-radio 也一样,推荐用 value 来绑定真实值。
3.4 前缀图标的“身份反转”
Element UI 里给输入框加图标,用 prefix-icon 属性:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移前:Element UI -->
<el-input v-model="username" prefix-icon="el-icon-user"></el-input>
Element Plus 把这个属性砍了,改成插槽。你需要自己引入图标组件,然后放进 #prefix 插槽里:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移后:Element Plus -->
<el-input v-model="username" placeholder="用户名">
<template #prefix>
<el-icon><User /></el-icon>
</template>
</el-input>
<script setup>
import { User } from '@element-plus/icons-vue'
</script>
注意:插槽必须写在 el-input 的子内容里,别写外面去了。后缀图标的 suffix-icon 同理,改成 #suffix 插槽。
四、行为差异:看起来像,用起来不像
4.1 事件名的小变化:close 和 closed
Element UI 的 el-dialog 只有一个 close 事件,代表关闭动作。Element Plus 拆成了两个:close 在关闭动画开始前触发,closed 在动画结束后触发。如果你需要在关闭后清空表单,用 close 可能会太早。
<!-- 技术栈:Vue 3 + Element Plus -->
<el-dialog v-model="dialogVisible" @close="onClose" @closed="onClosed">
<p>内容</p>
</el-dialog>
<script setup>
function onClose() {
// 刚点完关闭按钮,动画还没播
console.log('开始关')
}
function onClosed() {
// 动画结束了,彻底关了
console.log('已经关')
}
</script>
这个不算破坏性变更,但很容易让你写出“时机不对”的代码。我的建议是:清理数据用 closed,记录点击用 close。
4.2 表单校验的“脾气”变了
Element Plus 的表单校验在 Vue 3 下有一个让人挠头的点:当你在一个表单项上切换选项时,旧的错误提示可能不会自动消失。比如你选了一个非法邮箱,然后立刻切换到另一个合法的,错误信息还挂在页面上。这时候你需要手动清一下校验:
// 技术栈:Vue 3 + Element Plus
// 假设我们有一个表单实例 formRef
formRef.value.clearValidate('email')
老项目可能没有这种问题,因为 Vue 2 的响应式机制和 Element UI 的内部实现不同。迁移后别忘了多测测表单交互。
4.3 组件实例方法的位置变了
以前 this.$refs.form.validate() 直接调用,现在 Element Plus 的组件实例也支持类似方法,但你在 <script setup> 里必须先把 ref 声明好:
<!-- 技术栈:Vue 3 + Element Plus -->
<template>
<el-form ref="formRef" :model="form" :rules="rules">
<el-form-item prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
const formRef = ref()
const form = reactive({ name: '' })
const rules = { name: [{ required: true, message: '请输入姓名' }] }
function submit() {
formRef.value.validate((valid) => {
if (valid) {
console.log('校验通过')
} else {
console.log('校验失败')
}
})
}
</script>
注意:formRef 必须和模板里的 ref 同名,validate 回传的参数是布尔值,不是错误对象。这个和 Element UI 的用法差不多,但千万别把 form 和 formRef 搞混。
五、实战迁移:一个“登录弹窗”的重生
我们把上面所有的坑揉进一个登录弹窗里。老项目用 Vue 2 + Element UI,写成了这样:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移前:Vue 2 + Element UI -->
<el-dialog :visible.sync="loginVisible" title="登录" @close="handleClose">
<el-input v-model="username" placeholder="用户名" prefix-icon="el-icon-user"></el-input>
<el-input v-model="password" type="password" placeholder="密码" prefix-icon="el-icon-lock"></el-input>
<el-checkbox v-model="remember" label="记住我"></el-checkbox>
<el-button type="text" @click="forgotPassword">忘记密码?</el-button>
<el-button type="primary" size="medium" @click="login">登录</el-button>
</el-dialog>
<script>
export default {
data() {
return {
loginVisible: false,
username: '',
password: '',
remember: false
}
},
methods: {
handleClose() { console.log('关闭') },
forgotPassword() { console.log('跳转') },
login() { console.log('登录') }
}
}
</script>
迁移到 Vue 3 + Element Plus,要改的地方一目了然:弹窗用 v-model,输入框前缀图标改成插槽,复选框 label 和 value 分离,按钮 type="text" 改成 link,size="medium" 直接删掉。改完是这样:
<!-- 技术栈:Vue 3 + Element Plus -->
<!-- 迁移后:Vue 3 + Element Plus -->
<el-dialog v-model="loginVisible" title="登录" @close="handleClose">
<el-input v-model="username" placeholder="用户名">
<template #prefix>
<el-icon><User /></el-icon>
</template>
</el-input>
<el-input v-model="password" type="password" placeholder="密码">
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
<el-checkbox v-model="remember" label="记住我" value="remember"></el-checkbox>
<el-button link @click="forgotPassword">忘记密码?</el-button>
<el-button type="primary" @click="login">登录</el-button>
</el-dialog>
<script setup>
import { ref } from 'vue'
import { User, Lock } from '@element-plus/icons-vue'
const loginVisible = ref(false)
const username = ref('')
const password = ref('')
const remember = ref(false)
function handleClose() { console.log('关闭') }
function forgotPassword() { console.log('跳转') }
function login() { console.log('登录') }
</script>
这个例子几乎覆盖了所有常见坑:v-model、插槽、图标、按钮类型、尺寸、复选框。你照着这个思路去改其他页面,八九不离十。
六、应用场景与优缺点分析
6.1 什么时候升级,什么时候别折腾
如果你正在做一个新项目,直接上 Vue 3 + Element Plus 是明智的。Vue 3 的生态已经成熟,组合式 API 写起来更舒服,Element Plus 的按需导入也能让你的打包体积小不少。
如果你维护的是一个老项目,但老板还准备加新功能、长期维护,那早晚都要升级。早点升级,越拖越痛苦,因为老依赖会越来越难处理。但如果项目只是“活着”,没有新需求,也不知道哪天会下线,那我劝你别折腾。升级的成本可能比继续用老版本高出几倍,而且收益不明显。
6.2 Element Plus 的优点和槽点
优点很实在:底层性能更好,组件 API 更规范,类型提示完全对齐 TypeScript,而且支持按需导入。你用多少组件,就打包多少,不像 Element UI 那样一引入就是整个库。
槽点也不少:文档在某些地方写得比较简略,组件行为偶尔会让你意外,比如表单校验的时机、弹窗的事件顺序。另外,如果你完全不会 TypeScript,可能会觉得类型定义有点“碍眼”,但不影响正常使用。总体上说,Element Plus 是一套健康且有活力的组件库,但你需要花点时间适应它的“脾气”。
七、总结与注意事项
升级最怕的不是遇到问题,而是不知道会出什么问题。这里给大家一份“避坑备忘录”,照着检查,至少能少掉一半头发:
- 全局搜索
visible.sync,全部改成v-model。 type="text"改成link。size="mini"改成size="small",size="medium"直接删掉。prefix-icon和suffix-icon改成#prefix和#suffix插槽,配合el-icon使用。- 图标从
<i class="el-icon-xxx">换成el-icon加组件。 - 复选框和单选框的
label如果参与数据存储,用value显式设置实际提交的值。 - 消息提示从
this.$message换成导入的ElMessage。 - 注意弹窗的
close和closed事件,清理数据尽量用closed。 - 表单校验多测几遍,必要时手动
clearValidate。 - 升级前备份、搜代码、跑测试,三件套缺一不可。
迁移过程必然会遇到各种小石子,但不要慌。把报错信息翻译成人话,把页面一个个点过去,你会发现 Element Plus 也慢慢变得顺眼了。祝你升级顺利,头发还在。
评论
围绕“Vue2项目升级Vue3时Element Plus迁移避坑:组件废弃、属性变更与行为差异”参与讨论