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

ops/2024/9/25 21:03:40/

由于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/ops/115947.html

相关文章

MySQL5.7.42高可用MHA搭建及故障切换演示

系列文章目录 rpmbuild构建mysql5.7RPM安装包 MySQL基于GTID同步模式搭建主从复制 文章目录 系列文章目录前言一、MHA架构介绍1.MHA的功能2.MHA组成3.MHA故障转移过程4.MHA架构优缺点 二、环境准备1.服务器免密2.基于GTID主从复制搭建3.下载mha组件 三、MHA组件安装1.安装依赖…

TFTP协议

目录 一、TFTP协议概述 1.1 TFTP协议简介 1.2 TFTP协议特点 二、TFTP协议原理 2.1 TFTP协议工作流程 2.2 TFTP协议数据包格式 三、TFTP协议应用场景 3.1 网络设备配置文件传输 3.2 虚拟机镜像文件传输 3.3 IoT设备固件升级 四、TFTP协议优化方法 4.1 增加超时重传机…

大佬,简单解释下“嵌入式软件开发”和“嵌入式硬件开发”的区别

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;首先&#xff0c;嵌入式硬…

Vue 中 watch 的使用方法及注意事项

前言 Vue 的 Watch 是一个非常有用的功能&#xff0c;它能够监听 Vue 实例数据的变化并执行相应的操作。本篇文章将详细介绍 Vue Watch 的使用方法和注意事项&#xff0c;让你能够充分利用 Watch 来解决 Vue 开发中的各种问题。 1. Watch 是什么&#xff1f; 1.1 Watch 的作…

VScode配置连接远程服务器configure ssh Hosts

VScode配置连接远程服务器&#xff0c;具体步骤 一、点击VScode左下脚这两个∟的按钮 二、点击完上面的按钮后&#xff0c;出现如下的下拉选项&#xff0c;选择“Connect to Host” 三、选择“Connect to Host”后&#xff0c;下拉选项会更新&#xff0c;选择“Configure SSH …

nginx_单机平滑升级

#!/bin/bash# 定义要下载的 Nginx 源码包的 URL 和保存路径 nginx_tar"http://nginx.org/download/nginx-1.19.0.tar.gz" nginx_tar_file"/tmp/nginx-1.19.0.tar.gz" nginx_version"nginx-1.19.0" nginx_path$(which nginx) # 获取 Nginx 的路径…

Android Manifest权限清单

Android权限部分可分为安装权限、运行时权限、特殊权限。 其中安装权限分普通权限和签名权限&#xff1a;普通权限安装后就有&#xff0c;无需重新授权&#xff1b;签名权限则需要系统签名才有的权限&#xff1b; 特殊权限则需要打开指定的系统页面进行授权&#xff0c;当然使用…

毕业设计选题:基于ssm+vue+uniapp的面向企事业单位的项目申报小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…