前提
Vue2
的官方支持到2023年12月31日已经到期,需要对公司Vue
项目代码进行升级,但笔者公司前端项目使用Vue2.6
版本,已经迭代开发5年有余,代码量多且杂,依赖的npm包很多,并且版本也大多都很停留在支持Vue2.6版本,所以想一次性升级至3.0,难度和风险可想而知。因此,我采用先保证稳定的前提,再慢慢更新升级。那先升级至2.7是最佳的选择。升级后可采用Vue3的新语法,对团队内成员来说也多了一次学习、更新技能的机会,对以后的迭代和引进新技术(如计划使用的微前端)提供便利。
这里记录一下升级过程,及其中踩过的坑。
升级依赖
- 对
@vue/cli
升级如老项目是
v4
版本,则升级至v4
对应的最新版本,如果是v5
,则升级至v5
对应最新版本
- 将
vue
升级至^2.7.0
- 对
@vue/composition-api
进行处理,老项目如果之前引入过该包,一般情况下可直接删除,然后移除在入口文件中的相关代码
- 升级
eslint-plugin-vue
至^9.0
,升级该报的目的是为了防止使用<script setup>
时lint报错变量未使用的问题。 - 移除
vue-template-compiler
- 2.7 的单文件组件编译器使用了
PostCSS 8
(从 7 升级而来)。PostCSS 8
应该向下兼容了绝大多数插件,但是该升级可能在你使用了一些只支持PostCSS 7
的自定义插件时遇到问题。这种情况下,你需要升级相应的插件至其兼容PostCSS 8
的版本。 - 使用
volar
禁用vetur
升级依赖建议删除
node_modules
和相应包的lock文件,如package-lock.json
,然后进行重新安装。
setup 中使用 vuex、$message、$route等写法
vue-router 4.x版本存在一个较大的兼容性问题,即不支持在
mixin
中的路由钩子函数。考虑到实际项目中有这方面业务需求,所以并没有升级vuex
和vue-router
到 4.x 版本,在单文件组件中如果需要引用两者的话有一个替代方案在本地新建一个useVue.js
文件
/**
* 升级vue2.7辅助函数
*/
import { getCurrentInstance } from 'vue';
/** this.$store替换方案 */
export function useStore() {
const { proxy } = getCurrentInstance();
const store = proxy.$store;
return store;
}
/** this.$route替换方案 */
export function useRoute() {
const { proxy } = getCurrentInstance();
const route = proxy.$route;
return route;
}
/** this.$router替换方案 */
export function useRouter() {
const { proxy } = getCurrentInstance();
const router = proxy.$router;
return router;
}
/** this.$message方法替换方案 */
export function useMessage() {
const { proxy } = getCurrentInstance();
const message = proxy.$message;
return message;
}
然后在组合式api文件中写法如下:
import {useRouter, useMessage, useStore} from '@/hooks/useVue';
// this.$router.push 写法
const router = useRouter();
router.push('/home');
// this.$message 写法
const message = useMessage();
message({
type: 'error',
message: '提示弹窗',
})
// vuex写法
const store = useStore();
const stateA = computed(() => store.state.stateA);
const stateB = computed(() => store.state.stateB);
const methodA = store.dispatch('methodA', {name: '张三'});
setup 中使用 vuex 的 mapState等方法
setup
语法糖中不支持使用vuex
中的辅助函数如mapGetter
。项目中依然保持Vuex,且未升级至4.0的话,需要封装mapState,mapGetters,mapActions等方法。
- 创建 useMapper.js 文件,封装方法各个mapper方法
import { computed } from 'vue';
import { useStore } from './useVue'; // 上一步中的代码
export function useStateMapper(mapper, mapFn) {
const store = useStore();
const storeStateFns = mapFn(mapper);
const storeState = {};
Object.keys(storeStateFns).forEach((fnKey) => {
// vuex源码中mapState和mapGetters的方法中使用的是this.$store,所以更改this绑定
const fn = storeStateFns[fnKey].bind({ $store: store });
storeState[fnKey] = computed(fn);
});
return storeState;
}
export function useActionMapper(mapper, mapFn) {
const store = useStore();
const storeActionsFns = mapFn(mapper);
const storeAction = {};
Object.keys(storeActionsFns).forEach((fnKey) => {
storeAction[fnKey] = storeActionsFns[fnKey].bind({ $store: store });
});
return storeAction;
}
export function useMutationsMapper(mapper, mapFn) {
const store = useStore();
const storeMutationsFns = mapFn(mapper);
const storeMutations = {};
Object.keys(storeMutationsFns).forEach((fnKey) => {
storeMutations[fnKey] = storeMutationsFns[fnKey].bind({ $store: store });
});
return storeMutations;
}
- 新建 useVuex.js 文件 使用
createNamespacedHelpers
对各个mapper封装hooks函数
import { createNamespacedHelpers, mapActions, mapGetters, mapMutations, mapState } from 'vuex';
import { useActionMapper, useMutationsMapper, useStateMapper } from './useMapper';
// 类型检查
const checkType = (data) => {
return Object.prototype.toString.call(data);
};
/**
*
* @param {*} moduleName 模块名称
* @param {*} mapper state属性集合 ['name', 'age']
* @returns
*/
export function useState(moduleName, mapper) {
let mapperFn = mapState;
// 如果使用模块化,则使用vuex提供的createNamespacedHelpers方法找到对应模块的mapState方法
if (checkType(moduleName) === '[object String]' && moduleName.length > 0) {
mapperFn = createNamespacedHelpers(moduleName).mapState;
}
return useStateMapper(mapper, mapperFn);
}
/**
*
* @param {*} moduleName 模块名称
* @param {*} mapper getters属性集合 ['name', 'age']
* @returns
*/
export function useGetters(moduleName, mapper) {
let mapperFn = mapGetters;
// 如果使用模块化,则使用vuex提供的createNamespacedHelpers方法找到对应模块的mapGetters方法
if (checkType(moduleName) === '[object String]' && moduleName.length > 0) {
mapperFn = createNamespacedHelpers(moduleName).mapGetters;
}
return useStateMapper(mapper, mapperFn);
}
/**
*
* @param {*} moduleName 模块名称
* @param {*} mapper Mutations ['fn1', 'fn2']
* @returns
*/
export function useMutations(moduleName, mapper) {
let mapperFn = mapMutations;
// 如果使用模块化,则使用vuex提供的createNamespacedHelpers方法找到对应模块的mapGetters方法
if (checkType(moduleName) === '[object String]' && moduleName.length > 0) {
console.log('createNamespacedHelpers(moduleName): ', createNamespacedHelpers(moduleName));
mapperFn = createNamespacedHelpers(moduleName).mapMutations;
}
return useMutationsMapper(mapper, mapperFn);
}
/**
*
* @param {*} moduleName 模块名称
* @param {*} mapper 方法名集合 ['fn1', 'fn2']
* @returns
*/
export function useActions(moduleName, mapper) {
let mapperFn = mapActions;
// 如果使用模块化,则使用vuex提供的createNamespacedHelpers方法找到对应模块的mapActions方法
if (checkType(moduleName) === '[object String]' && moduleName.length > 0) {
mapperFn = createNamespacedHelpers(moduleName).mapActions;
}
return useActionMapper(mapper, mapperFn);
}
- 使用方式
<template>
<div class="home">
<span>姓名:{{name}} 年龄:{{age}} 性别:{{sex}}</span>
<button @click="changeName">改名</button>
</div>
</template>
<script setup>
import {useState, useActions} from '@/hooks/useVuex'
const storeState = useState('home', ['name', 'age', 'sex'])
const storeActions = useActions('home', ['setName'])
const changeName = () => {
storeAction.setName('李四')
}
</script>
深度选择器改写 ::v-deep、/deep/为:deep()
更新后,如果有::v-deep、/deep/相关的报错或者警告,需要改用:deep()
<style scoped>
.a :deep(.b) { /* ... */ }
</style>
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 chaoyumail@126.com