前端工程记录:Vue2 typescript项目升级Vue3

news/2024/9/28 5:07:23/

由于typescript飞速发展,某些vue2项目也在vue3出现之前集成了typescript开发,例如我的个人网站,当时花费了不少时间。而vue3我使用一段时间后,在2022年左右开始投入生产,但是这个个站就没怎么维护了。若是想继续,升级是无法避开,毕竟vue2也不怎么熟悉了

1 依赖升级

1.1 老项目依赖 - vuex-module-decorators+vue-property-decorator

老项目使用装饰器包vue-property-decorator实现vue组件的ts支持,使用vuex-module-decorators实现vuex状态管理的支持。

  "scripts": {"dev": "vue-cli-service serve","build": "vue-cli-service build","lint": "vue-cli-service lint"},"dependencies": {"lz-string": "~1.4.4","tslib": "~2.1.0","vue": "2.6.10","vue-router": "~3.5.1","vuex": "~3.6.2"},"devDependencies": {"@types/js-cookie": "3.0.6","@types/lz-string": "^1.3.33","@vue/cli-plugin-typescript": "~3.11.0","@vue/cli-service": "~3.11.0","babylonjs": "~4.1.0","js-cookie": "~2.2.1","sass": "~1.18.0","sass-loader": "~7.1.0","ts-loader": "~6.2.1","typescript": "~3.7.2","vue-meta": "~2.3.3","vue-property-decorator": "8.5.1","vue-template-compiler": "2.6.10","vuex-module-decorators": "^0.11.0","webpack": "4.47.0"},

1.2 新项目ts依赖

  "scripts": {"dev": "vite --port 5174","build": "vue-tsc && vite build","preview": "vite preview"},"dependencies": {"babylonjs": "~4.1.0","dayjs": "^1.11.11","lz-string": "^1.5.0","vue": "^3.3.4","vue-router": "^4.2.5"},"devDependencies": {"@types/node": "^20.14.9","@vitejs/plugin-vue": "^4.2.3","@vitest/ui": "^1.6.0","@vue/test-utils": "^2.4.6","autoprefixer": "^10.4.19","jsdom": "^24.1.0","postcss": "^8.4.39","sass": "^1.69.5","sass-loader": "^14.2.1","tailwindcss": "^3.4.4","typescript": "^5.0.2","vite": "^4.4.5","vitest": "^1.6.0","vue-tsc": "^1.8.5"}

1.3 升级思路

1.3.1 vue3直接支持typescript,所以去掉了装饰器式的组件定义
1.3.2 去掉vuex依赖

实际上,因为状态比较简单,直接使用vue3的reactive定义响应式状态属性state,其它的方法从actions里面提取出来,这样改动很小,每个compute属性返回一个vue3 computed的计算属性

如果是比较复杂的项目,可以考虑前期这样,后面替换成pinia,实际上我个人是不推荐使用pinia的,除非有kpi需求等

1.3.3 修改配置

这个是必须的,主要是webpack和vite配置的升级

1.3.4 修改组件代码 

这个是工作量最大的,下面会讲一些注意点

1.3.5 创建vue3项目将老代码和依赖移过去(推荐)

 这是我实践且推荐的方法,注意目录结构不要变,一点点的移过去会更稳

2 webpack升级到vite的配置

比较详细的官方会有,这里只是讲一些关键点

2.1 新增vite配置文件

新增vite.config.ts,配置如下(如果是采用1.3.5的推荐方法可跳过)

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({plugins: [vue()],build: {rollupOptions: {//},},resolve: {alias: {"@": path.resolve(__dirname, "./src"),},},server: {proxy: {},},
});

 2.2 更新外部依赖配置externals

很多时候,我们会把比较大的包或常用的包通过url引入,这时候就需要修改配置,例如vue.config.js的配置如下

const path = require('path');module.exports = {chainWebpack: (config) => {config.externals({vue: 'Vue',});},
};

vue3则需要安装依赖

yarn add vite-plugin-external -D

vite.config.ts中需要使用插件: 

import { defineConfig } from 'vite';
import createExternal from 'vite-plugin-external';
export default defineConfig({plugins: [vue(),createExternal({externals: {vue: 'Vue'}})],
});

2.3 更新代理服务器配置

vue.config.js的配置如下:


module.exports = {devServer: {port: 8080,proxy: {'^/api': {target: 'https://api.xxx.fun',// ws: true,changeOrigin: true,pathRewrite: {'^/api': 'aaa',},onProxyReq: function (request) {request.setHeader('origin', 'https://www.xxx.fun');},},},},
};

在vite.config.ts中则对应:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";export default defineConfig({server: {proxy: {"^/api": {target: "https://api.xxx.fun",// ws: true,changeOrigin: true,rewrite: (path) => path.replace(/^\/api/, "/xxx"),headers: {Origin: "https://www.xxx.fun",},},},},
});

 可以较大的变化就是头的修改和pathrewrite了,更多详细信息见:开发服务器选项 | Vite 官方中文文档

2.3 入口html文件调整

2.3.1 将index.html从public移出至更目录
2.3.2 在body最下面新增es入口

即   <script type="module" src="/src/main.ts"></script>

2.3.3 去掉以前的baseurl配置

vue2项目支持的html模板语法<%= BASE_URL %>vite下不再默认支持,去掉即可,然后修改index.html文件即可

3 常用优化策略迁移

3.1 摇树优化treeshake默认支持

由于rollup默认支持treeshake,所以可以去掉vue2的相关配置,也就是package.json中的sideEffects字段

3.2 分包优化的调整-指定分包

在vue2中分包方式和vue3没变化,都是使用import函数,但是有一点区别:

  • vue2中未命名分包会进入chunk,vue3会是一个单独的文件
  • vue2命名分包可使用下面的方式,vue3不生效需要删除注释
import(/* webpackChunkName: "aaa" */ './AAA.vue')
  • vue3中在vite.config.ts中配置:
export default defineConfig({plugins: [vue({})],build: {rollupOptions: {output:{manualChunks(id) {if (id.includes("AAA.vue")) {return 'aaa'}}}},},
});

3.3 小文件引入

vue2支持用require引入文件,vue3也支持使用file-loader,所以变动不大。

4 组件和状态迁移

4.1 组件代码迁移

关键步骤就是:

  • script 新增 setup
  • 去掉class和decorator
  • Prop定义使用defineProps
  • state定义使用reactive
  • compute使用computed
  • slot使用v-slot,子组件使用时用<template></template>包括实现slot的插入使用

例如一个多语言组件

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { ClientModule } from '@/store/client';
import { parseLangText } from '@/utils/basic';
@Component({ name: 'lang' })
export default class extends Vue {@Prop({required: true})private val: any;protected render(h: any) {const lang = ClientModule.lang;const [txt, fl] = parseLangText(this.val, lang);return h('span',{ class: fl === 'en' ? 'english' : 'chinese', ...this.$props },txt);}
}
</script>

可以转化为

<script lang="ts" setup>
import { computed } from "vue";
import { langModule } from "../../modules";const mod = langModule();
const props = defineProps<{val: any;
}>();const text = computed(() => {return mod.parseText(props.val);
});const classList = computed(() => {return mod.lang === "en" ? "english" : "chinese";
});
</script>
<template><span :class="classList"> {{ text }} </span>
</template>

4.2 vuex store迁移

关键点就是:

  • 用类或者一个js对象代替store,state用reactive定义实现响应式
  • 计算属性使用computed替代
  • action/mutation都定义为一个对象方法

例如 一个简单的store

import {VuexModule,Module,Mutation,Action,getModule
} from 'vuex-module-decorators';
import store from '../';
import { IMedia } from '@/types/media';
import Vue from 'vue';export interface IPlayerState {audioList: IMedia[];
}@Module({ dynamic: true, store, namespaced: true, name: 'player' })
class Player extends VuexModule implements IPlayerState {public audioList: IMedia[] = [];public target: IMedia | null = null;public get audioPlay() {return this.audioList.find(ele => ele.playing);}@Mutationpublic play() {if (this.target) {Vue.set(this.target, 'playing', true);}}@Mutationpublic setTarget(e: IMedia | null) {if (this.target && e && this.target.src === e.src) {return;}if (e !== null) {e.playing = false;}this.target = e;}@Mutationpublic stop() {if (this.target) {Vue.set(this.target, 'playing', false);}}
}export const PlayerModule = getModule(Player);

可以转化成

import { IMedia } from "../../types";
import { reactive,computed} from "vue";type MediaPlayerInfo = {audioList: IMedia[];target: IMedia | null;
};export class PlayerManager {static initialState() {return {audioList: [],target: null,};}static build(stateBuilder: (s: MediaPlayerInfo) => ObjectState<MediaPlayerInfo>) {return new PlayerManager(this.initialState());}
public readonly state:MediaPlayerInfoconstructor(state: MediaPlayerInfo) {
this.state=reactive(state)
}audioPlaying() {return computed(()=>this.state["audioList"].find((ele) => ele.playing));}target() {return computed(()=>this.state.target);}public play() {const target = this.state.target;if (target) {target.playing = true;target.playing = true;}}public setTarget(e: IMedia | null) {const target = this.state.target;if (target && e && target.src === e.src) {return;}if (e !== null) {e.playing = false;}this.state.target= e;}public stop() {const target = this.state.target;if (target) {target.playing = false;}}
}

4.3 迁移策略

4.3.1 从页面组件或store模块作为一次任务

避免漏掉,一个个的完成

4.3.2 从最细粒度的开始迁移

也就是页面所用到的最小组件开始,这样可以避免过多的报错导致代码工具或者提示不可以

4.3.3 多commit代码

哪怕没完成,也不要在未commit的时候撤销等待,避免浪费工作量

5 版本管理

5.1 可在新分支新目录下存放全部的代码

这样的好处是merge等不会出现冲突

5.2 老版本核心依赖版本,用~而不是^

例如vue2/vue-router/vuex,锁定小版本,写固定版本最好。这样的好处就是不用担心老项目出现大的变化,vue2有些版本还是会出现breaking change的,这也是我对vue比较揪心的。例如vue2.7就让一些slot不可以了。

希望看完这篇文章对你有所帮助,写了一个小时,也该休息了。看完的你也是,如果有什么好的心得和补充,欢迎留言~

2cy

YU.H


http://www.ppmy.cn/news/1531448.html

相关文章

【LLM】 TinyAgent 构建指南

文章目录 TinyAgent 构建指南项目概述实现步骤步骤一&#xff1a;模型构建步骤二&#xff1a;工具构建步骤三&#xff1a;Agent 构建步骤四&#xff1a;运行 Agent 结论参考文献学习心得 TinyAgent 构建指南 项目地址&#xff1a;https://github.com/datawhalechina/tiny-univ…

【深度学习】05-Rnn循环神经网络-01- 自然语言处理概述/词嵌入层/循环网络/文本生成案例精讲

循环神经网络&#xff08;RNN&#xff09;主要用于自然语言处理的。 循环神经网络&#xff08;RNN&#xff09;、卷积神经网络&#xff08;CNN&#xff09;和全连接神经网络&#xff08;FCN&#xff09;是三种常见的神经网络类型&#xff0c;各自擅长处理不同类型的数据。下面…

【Go语言】深入解读Go语言中的指针,助你拨开迷雾见月明

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

虚拟机文件系统根目录上的磁盘空间不足?VMware虚拟机扩容磁盘步骤讲解

VMware虚拟机扩容磁盘步骤讲解 今天使用vmware&#xff0c;想使用Ubuntu虚拟机&#xff0c;结果出现这种情况&#xff1a; 我的环境&#xff1a; Ubuntu20.04 VMWare workstation pro 17 VMware设置 参考链接&#xff1a; https://blog.csdn.net/hktkfly6/article/details…

Sqlite_Datetime列选择三月的行

In SQLite, use the strftime function to extract components from a date/time value SELECT * FROM table WHERE strftime(%m, datemonth) 03;strftime(‘%m’, datemonth): extracts the month part from the datemonth column as a string (with leading zeros for sing…

50道渗透测试面试题,全懂绝对是高手

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330&scene21#wechat_redirect 《网安面试指南》…

Unity图形用户界面!*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。(万字解析)

Unity 3D GUI 简介 游戏开发过程中&#xff0c;开发人员往往会通过制作大量的图形用户界面&#xff08; Graphical User Interface&#xff0c;GUI &#xff09;来增强游戏与玩家的交互性。 Unity 3D 中的图形系统分为 OnGUI、NGUI、UGUI等&#xff0c;这些类型的图形系统内容…

国产数据库盘点-亚信安慧AntDB独立模式部署

本章节主要介绍 AntDB 集中式独立模式部署&#xff0c;主要分两步&#xff1a; 安装数据库&#xff08;二进制文件&#xff09; 数据库初始化 安装数据库 以 AntDB 7.2 版本的安装包 antdb-t-ee-release-7.2.generic.* 举例。 将安装包&#xff08;antdb-t-ee-release-7.2…