前言
2020年9月18日,vue3正式版发布了,前几天把文档整体读了一遍,感触很深,可以解决我项目中的一些痛点,于是就决定重构之前那个vue2的开源项目。
本篇文章就记录下重构vue2项目的过程,欢迎各位感兴趣的开发者阅读本文。
环境搭建
本来打算使用vite + vue3 + VueRouter + vuex + typescript
来构架项目的,但是经过一番折腾后发现vite
目前只对vue支持,对于vue周边的一些库还没做到支持,没法在项目中使用。
最后,还是决定使用Vue Cli 4.5
来构建了。
虽然vite目前还无法正常在项目中使用,但是我也折腾了一回,就记录下在折腾时的过程以及一些报错。
使用vite构建项目
本文采用的包管理工具为yarn
,将其升级至最新版本就可以正常创建vite项目了。
初始化项目
接下来,我们来看看具体步骤。
- 打开终端,进入你的项目目录,运行命令:
yarn crete vite-app vite-project
,该命令用于创建一个名为vite-project
的项目。
- 创建完成后,会得到如下所示的文件。
- 进入创建好的项目,运行命令:
yarn install
,该命令会安装package.json
中声明的依赖。
- 我们使用
IDE
打开刚才创建的项目,整体项目如下所示,vite官方为我们提供了一个简单的demo。
- 打开
package.json
查看启动命令在终端运行命令:yarn run dev
或者点击ide
的运行图标来启动项目。
- 大功告成,浏览器访问
http://localhost:3000/
,如下所示。
集成Vue周边库
我们将Vue CLI
初始化的项目文件替换到用vite
初始化的项目中去,然后修改packge.json
中的相关依赖,然后重新安装依赖即可。
具体过程如下:
- 替换文件,替换后的项目目录如下所示。
- 从
package.json
中提取我们需要的依赖,提取后的文件下。
{ "name": "vite-project", "version": "0.1.0", "scripts": { "dev": "vite", "build": "vite build" }, "dependencies": { "core-js": "^3.6.5", "vue": "^3.0.0-0", "vue-class-component": "^8.0.0-0", "vue-router": "^4.0.0-0", "vuex": "^4.0.0-0" }, "devDependencies": { "vite": "^1.0.0-rc.1", "@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/parser": "^2.33.0", "@vue/compiler-sfc": "^3.0.0-0", "@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-typescript": "^5.0.2", "eslint": "^6.7.2", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-vue": "^7.0.0-0", "node-sass": "^4.12.0", "prettier": "^1.19.1", "sass-loader": "^8.0.2", "typescript": "~3.9.3" }, "license": "MIT" }
8abcc9f5b934568e54c0229c6663866c
- 启动项目,没报错,嘴角疯狂上扬。
- 浏览器访问后,空白页面,打开
console
后,发现main.js 404
难搞,找不到main.js
,那我把main.ts
后缀改一下试试。将后缀改成js
后,文件是不报错404了,但是又有了新的错误。
vite服务500和@别名无法识别,于是我打开ide的控制台看了错误,大概是scss的错,vite还没支持scss。
scss不支持,别名不识别,网上找了一圈也没找到解决方案,这些最基础的东西都无法被vite支持,那它就不能用在项目中了,于是我放弃了。
综合上述,vite
要走的路还有很多,等它在社区成熟了,再将它应用到项目中吧。
使用Vue Cli构建项目
由于vite
的不合适,我们还是继续选择用webpack
,此处我们选择用Vue CLI 4.5
来创建项目。
初始化项目
- 在终端进入项目目录,执行命令:
vue create chat-system-vue3
该命令用于创建一个名为chat-system-vue3
的项目。
- 创建完成后,如下所示。
- 用
IDE
打开项目,打开package.json
文件,查看项目启动命令或者直接点编译器的运行按钮。
- OK,大功告成,打开浏览器,访问终端的内网地址。
解决报错问题
在浏览CLI
默认创建的demo时,打开main.js
文件发现其中App.vue
文件报类型错误,无法推导出具体的类型。
一开始,我也懵逼,想起了Vue文档所说的,启用TypeScript必须要让 TypeScript 正确推断 Vue 组件选项中的类型,需要使用 defineComponent
。
App.vue文件代码如下:
<template> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view /> </template> <style lang="scss"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } #nav { padding: 30px; a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: #42b983; } } } </style>
观察代码后我们发现CLI
生成的代码没有包含文档中所描述的代码,因此我们将其补充上,然后导出即可。
import { defineComponent } from "vue"; const Component = defineComponent({ // 已启用类型推断 }); export default Component;
加入上述代码后,我们的代码就不报错了。
根据官网描述,我们可以在defineComponent
的包裹中写组件的逻辑代码,但是我看了CIL提供的demo的Home
组件后发现,他的写法如下。
export default class Home extends Vue {}
在项目的src
目录下有一个名为shims-vue.d.ts
的文件,它声明了所有vue文件的返回类型,因此我们可以按照上述方法来写。该声明文件代码如下。
declare module "*.vue" { import { defineComponent } from "vue"; const component: ReturnType<typeof defineComponent>; export default component; }
这样的写法看起来更符合TypeScript,不过这种写法写法只支持部分属性,同样的我们组件的逻辑代码写在类内部即可,那么将刚才App.vue
文件中做的更改也应用到此处,如下所示。
<script lang="ts"> import { Vue } from "vue-class-component"; export default class App extends Vue {} </script>
class
写法支持的属性如下图所示:
image-20201009210815033
配置IDE
此处内容仅适用于webstorm
,如果编辑器是其他的可跳过本部分。
我们在项目中集成了eslint
和prettier
,默认情况下webstorm
是没有启用这两个东西的,需要我们自己手动开启。
- 打开webstorm的配置菜单,如下所示
image-20201006153458084
- 搜索
eslint
,按照下图所示进行配置,配置完成后点APPLY
、OK
即可。
image-20201006153031544
- 搜索
prettier
,按照下图所示进行配置,配置完成后点APPLY
、OK
即可。
image-20201006153654226
配置完上面的内容后,还有一个问题,在组件上用v-if
v-for
等vue指令时没有提示,这是因为webstorm没法正确读取node_modules
包,按照下述操作即可解决这一问题。
image-20201006154114315
执行上述操作后,等待时间根据cpu
性能而定,届时电脑会发热。这都是正常现象
image-20201006154306682
成功后,我们发现编辑器已经可以正常识别v-
指令了,并且给了相应的提示。
image-20201006154454592
项目目录对比
按照上述步骤,即可创建一个vue3
的项目,接下来我们将需要重构的vue2
项目的目录与上面创建的项目进行下目录对比。
- 如下所示,为
vue2.0
项目的目录
image-20201006162826706
- 如下所示,为
vue3.0
项目的目录
image-20201006162936370
仔细观察后,我们发现在目录上并没有什么大的区别,只是多了typescript
的配置文件和项目内使用ts的时辅助文件。
项目重构
接下来,我们来一步步把vue2项目的文件迁移到vue3项目中,修改不合适的地方,让其适配vue3.0。
适配路由配置
我们先从路由配置文件开始适配,打开vue3项目的router/index.ts
文件,发现有一个报错,报错如下。
image-20201006215331894
错误信息是类型没被推导出来,我看了下面路由的写法后,盲猜它需要用函数返回,于是试了下,还真就是这样,正确的路由写法如下。
{ path: "/", name: "Home", component: () => Home }
整体的路由配置文件代码如下:
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"; import Home from "../views/Home.vue"; const routes: Array<RouteRecordRaw> = [ { path: "/", name: "Home", component: () => Home }, { path: "/about", name: "About", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ "../views/About.vue") } ]; const router = createRouter({ history: createWebHashHistory(), routes }); export default router;
我们再来看看vue2
项目中的路由配置,为了简单起见我摘抄了部分代码过来,如下所示。
import Vue from 'vue' import VueRouter from 'vue-router' import MsgList from '../views/msg-list' import Login from "../views/login" import MainBody from '../components/main-body' Vue.use(VueRouter); const routes = [ { path: '/', redirect: '/contents/message/message', }, { name: 'contents', path: '/contents/:thisStatus', // 重定向到嵌套路由 redirect: '/contents/:thisStatus/:thisStatus/', components: { mainArea: MainBody }, props: { mainArea: true }, children: [ { path: 'message', components: { msgList: MsgList } } ], }, { name: 'login', path: "/login", components: { login:Login } } ]; const router = new VueRouter({ // mode: 'history', routes, }); export default router
经过观察后,它们的不同点如下:
Vue.use(VueRouter)
这种写法被移除new VueRouter({})
写法改为了createRouter({})
- hash模式和history模式声明由原先的
mode
选项变更为了createWebHashHistory()
和createWebHistory()
更加语义化了 - 声明路由时多了ts的类型注解
Array<RouteRecordRaw>
知道它们的区别后,我们就可以对路由进行适配和迁移了,迁移完成的路由配置文件:router/index.ts
这里有个小坑,路由懒加载的时候必须给他返回一个函数。例如:
component: () => import("../views/msg-list.vue")
。不然就会报黄色警告。
image-20201015223425458
image-20201015223525227
适配Vuex配置
接下来我们来看看两个版本在vuex
使用上的区别,如下所示为vue3
的vuex配置。
import { createStore } from "vuex"; export default createStore({ state: {}, mutations: {}, actions: {}, modules: {} });
我们再来看看vue2
项目中的vuex配置,为了简洁起见,我只列出了大体代码。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); export default new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { } })
经过对比后,我们发现的不同点如下所示:
- 按需导入
import { createStore } from "vuex"
,移除了之前的整个导入import Vuex from 'vuex'
- 移除了
Vue.use(Vuex)
的写法 - 导出时丢弃之前的
new Vuex.Store
写法,改用了createStore
写法。
知道上述不同点后,我们就可以对代码进行适配和迁移了,迁移完成的vuex配置文件:store/index.ts