聲明
請(qǐng)注意,筆者在寫本文時(shí)归薛,已經(jīng)完成了升級(jí)改造工作谍憔,故部分錯(cuò)誤可能無法通過截圖呈現(xiàn)匪蝙,不過筆者會(huì)盡可能用語(yǔ)言描述清楚
一次失敗的嘗試
由于所要改造項(xiàng)目是公司運(yùn)行六七年的老項(xiàng)目了,內(nèi)容之大之重可以想象习贫,純靠人力的話逛球,光是想想就頭大
所謂遇事不決先百度,像這類遷移工具必定是有人已經(jīng)做過了的沈条,于是發(fā)現(xiàn)了gogocode
首先需忿,使用npm
安裝該工具
npm install gogocode-cli -g
接著進(jìn)行語(yǔ)法轉(zhuǎn)換
在項(xiàng)目根目錄執(zhí)行如下命令
// src是要轉(zhuǎn)換的源
gogocode -s ./src -t gogocode-plugin-vue -o ./src-out
然后靜靜等待它轉(zhuǎn)換完成
下一步诅炉,對(duì)相關(guān)依賴做升級(jí)蜡歹,gogocode
也提供了相關(guān)指令
gogocode -s package.json -t gogocode-plugin-vue -o package.json
這一步執(zhí)行之后,使用yarn
重新安裝依賴
yarn
這樣工具幫我們處理的工作就完成了涕烧,我們先來跑一下lint
看看月而,如下有3k+
此時(shí),我已經(jīng)有點(diǎn)打退堂鼓了
先不急议纯,咱們先來看看它轉(zhuǎn)出來的效果
首先父款,它對(duì)package.json
轉(zhuǎn)換后,vue
的版本是3.0.0
瞻凤。我為什么要特意強(qiáng)調(diào)這個(gè)呢憨攒?
因?yàn)檫@說明了兩個(gè)問題:
1.現(xiàn)在vue
都3.3
了,它卻還在用3.0
阀参,這說明大概率它已經(jīng)鮮有維護(hù)更新了
2.3.3
的語(yǔ)法與3.0
大概率會(huì)存在出入肝集,也就是說,它的轉(zhuǎn)換結(jié)果未必可信
現(xiàn)在蛛壳,我們?cè)賮砜淳唧w的sfc
文件的轉(zhuǎn)換
- props
我們知道杏瞻,vue3.3
中props
已經(jīng)被扁平化了,是不需要通過特定的props
屬性進(jìn)行傳遞的衙荐,但gogocode
對(duì)這一部分并未處理
- attrs
attrs
也有類似的問題
- style樣式
工具對(duì)源碼轉(zhuǎn)換后捞挥,會(huì)導(dǎo)致style
標(biāo)簽內(nèi)的樣式錯(cuò)亂,具體來說應(yīng)該是它無法識(shí)別css
代碼中的注釋導(dǎo)致的
- 格式
除了上述的語(yǔ)法轉(zhuǎn)換問題外忧吟,還有其他的砌函,此處不再一一闡述。其實(shí)真正讓我決定放棄的是溜族,它轉(zhuǎn)換出來感覺有點(diǎn)亂讹俊,且對(duì)源碼的侵入性太大
比如eventBus
,修改后斩祭,無法和原來一樣調(diào)用劣像,需要先導(dǎo)入,再使用摧玫,且必須手動(dòng)傳入組件實(shí)例耳奕,偽代碼如下
import {$on,$emit,$off} from '../xx/utils'
$on(instance,key,callback)
這成功給筆者造成了將近一千個(gè)error
绑青,因?yàn)轫?xiàng)目里用到了400
多次
具體來說,每一個(gè)文件都會(huì)觸發(fā)相對(duì)路徑必須在絕對(duì)路徑之后引入
和變量未被使用
這兩個(gè)error
半自動(dòng)化
現(xiàn)在屋群,筆者打算自己手動(dòng)修正闸婴。不過在開始前,先說原則芍躏,原則就是邪乍,能不修改原來代碼的就盡量不修改,能保證原有調(diào)用格式的就盡量保證原來的使用方式
loaders
一些通用的調(diào)整对竣,可以通過webpack
的loader
來自動(dòng)化
- filters
vue3
中已經(jīng)剔除了filters
選項(xiàng)庇楞,需要將其移動(dòng)到methods
中
不過,代碼有點(diǎn)多否纬,咱這文章畢竟也不是賣錢的小冊(cè)吕晌,所以就只講一下思路,這也是最重要的:
首先临燃,通過正則提取出script
標(biāo)簽的內(nèi)容部分睛驳,然后將其ast
化,在通過節(jié)點(diǎn)樹找到export default
部分并將其提取出來轉(zhuǎn)換成對(duì)象
轉(zhuǎn)對(duì)象的時(shí)候膜廊,由于部分代碼可能是export default
外部的乏沸,比如
const yyy = 'spp'
export default {
data(){
return xxx:yyy
}
}
這部分直接去解析會(huì)報(bào)錯(cuò),因此需要進(jìn)行try...catch并在捕獲到錯(cuò)誤時(shí)遞歸
function parse(){
try{
...
}catch(err){
if(err){
parse()
}
}
}
接著判斷對(duì)象中是否存在filters
屬性爪瓜,如果有蹬跃,就對(duì)該屬性做遍歷,將其key
提取到methods
中钥勋,形式大概是這樣
export default {
methods:{
filtersKey:filtersValue
}
}
這個(gè)filtersValue
對(duì)應(yīng)的就是原來的過濾函數(shù)炬转,要把它生成到script
標(biāo)簽中,具體來說算灸,是import
語(yǔ)句和export
之間
function filtersValue(){
...
}
下一步扼劈,就是去template
模板中查找使用過濾函數(shù)的地方進(jìn)行替換。這利用正則可以很輕松的完成
- props
在vue2
中可以這樣聲明props
export default {
props:{
xxx:'some value'
}
}
在vue3
中會(huì)報(bào)錯(cuò)菲驴。因此需要對(duì)此進(jìn)行轉(zhuǎn)換荐吵。具體來說,就是找到props對(duì)象赊瞬,分別對(duì)它的key對(duì)應(yīng)的值進(jìn)行識(shí)別先煎。如果是對(duì)象或函數(shù)則不處理,如果是基本數(shù)據(jù)類型巧涧,則轉(zhuǎn)換成如下形式
{
type:'原值所對(duì)應(yīng)的數(shù)據(jù)類型',
default:'some value'
}
這里比較容易出錯(cuò)的點(diǎn)是關(guān)于字符串類型的獲取薯蝎,需要手動(dòng)拼接上引號(hào)
完整代碼如下
- bus通信
眾所周知,vue3
中已經(jīng)剔除了eventBus
谤绳,無法再通過new
實(shí)例的方式來實(shí)現(xiàn)跨級(jí)通信
但第三方插件又無法保證this
指向占锯,一般需要手動(dòng)傳入
但前文我們說過袒哥,要盡可能保證原調(diào)用方式不變
因此,需要在loader
中提取并添加
這里的注意點(diǎn)在于loader
的執(zhí)行時(shí)機(jī)消略,必須要保證它在源代碼被轉(zhuǎn)換之前堡称。否則添加的this
與回調(diào)函數(shù)內(nèi)的this
大概率不是一個(gè)。如下艺演,是經(jīng)過轉(zhuǎn)換后的代碼却紧,在回調(diào)中使用的this
其實(shí)并不是實(shí)際傳入的this
var this1 = this
this.$bus.$on(key,this,()=>{
this1.xxx = 'spp'
})
fixed
還有一些是可以在全局去做兼容的,它們的目的是與改造前的代碼行為盡可能保持一致
- bus通信
在前文loader中雖然解決了eventBus的this指向問題胎撤,但還沒有找到可替代的包
幸運(yùn)的是晓殊,筆者在之前實(shí)現(xiàn)的web-localstorage-plus中實(shí)現(xiàn)了該功能
首先,使用web-localstorage-plus
提供的接口來代替bus
功能
接著將其掛載到app.config.globalProperties
上哩照,這是vue3中提供的類似Vue.prototype
掛載方式
又不幸的是挺物,web-localstorage-plus
目前其實(shí)并不支持接收this
參數(shù),且回調(diào)函數(shù)也不支持傳遞多個(gè)參數(shù)
因此飘弧,還需要對(duì)這兩個(gè)接口進(jìn)行重寫。如下砚著,接受參數(shù)二并將其掛載到第三個(gè)函數(shù)參數(shù)上次伶,然后在觸發(fā)on
注冊(cè)的監(jiān)聽函數(shù)時(shí)手動(dòng)使用call
修改其this
指向,并將參數(shù)使用展開運(yùn)算符傳遞以支持多個(gè)參數(shù)傳遞
- vue-router
在筆者的業(yè)務(wù)中稽穆,對(duì)單點(diǎn)登錄進(jìn)行了校驗(yàn)冠王,并在無權(quán)限時(shí)跳轉(zhuǎn)到指定的頁(yè)面,偽代碼如下
// 獲取權(quán)限
this.$router.onReady(...)
// 校驗(yàn)權(quán)限
this.$router.beforeEach(...)
這兩行代碼有兩個(gè)問題
一個(gè)是vue-router4.x
中已經(jīng)廢棄了onReady
舌镶,需要改成isReady
代替
另一個(gè)問題是柱彻,當(dāng)頁(yè)面刷新或執(zhí)行window.open
時(shí),其表現(xiàn)與vue-router3.x
不一致
在vue-router3.x
中刷新并不會(huì)觸發(fā)beforeEach
鉤子餐胀,但vue-router4.x
中會(huì)觸發(fā)
這就導(dǎo)致哟楷,會(huì)觸發(fā)權(quán)限的重新獲取,而此時(shí)beforeEach
鉤子執(zhí)行時(shí)還拿不到權(quán)限數(shù)據(jù)導(dǎo)致跳轉(zhuǎn)到noAuth
頁(yè)面
因此否灾,需要對(duì)beforeEach
鉤子進(jìn)行重寫卖擅。如下,我們?cè)陧?yè)面刷新或window.open
被執(zhí)行時(shí)向本地設(shè)置緩存墨技,并在beforeEach
鉤子中判斷是否存在緩存標(biāo)記惩阶,存在則什么都不做,否則正常跳轉(zhuǎn)路由
- $children
vue3
中已經(jīng)剔除了$children
接口扣汪,需要自己手動(dòng)實(shí)現(xiàn)一份查找邏輯断楷。如下,只需要按指定的格式進(jìn)行遞歸即可
- $filters
對(duì)于注冊(cè)的全局過濾器崭别,現(xiàn)在統(tǒng)一調(diào)整到app.config.globalProperties
上
- view-ui-plus
之前默認(rèn)注冊(cè)到全局的loading現(xiàn)在已經(jīng)沒有了冬筒,需要根據(jù)業(yè)務(wù)需求做調(diào)整
其他
剩下的统刮,就是需要手動(dòng)調(diào)整的其他語(yǔ)法了
- vue-router
1-h函數(shù)
在vue4.x
版本中應(yīng)該已經(jīng)不支持render
函數(shù)了,由于筆者公司項(xiàng)目有且僅有這一處對(duì)組件的使用账千,故修改成了默認(rèn)方式
實(shí)際上侥蒙,更準(zhǔn)確的寫法應(yīng)該是這樣
2-注冊(cè)方式
4.x
中已經(jīng)不需要使用new
關(guān)鍵字了,取而代之的是createRouter
接口匀奏。另外鞭衩,mode
屬性也被history
替代了
import { createRouter, createWebHashHistory } from 'vue-router';
export default createRouter({
history: createWebHashHistory(),
routes:[...]
})
- vuex
1-注冊(cè)方式
與vue-router
類似,通過createStore
替換娃善。
import { createStore } from 'vuex';
export default createStore({...})
2-日志引入
它的日志插件的導(dǎo)入方式要改成下邊這樣
import { createLogger } from 'vuex';
- view-ui-plus
對(duì)于js
模塊中使用的消息提示需要手動(dòng)按需導(dǎo)入
import { Message } from 'view-ui-plus'
Message.error('...')
- h函數(shù)
h函數(shù)的變動(dòng)大致有四個(gè)
1-props與attrs
現(xiàn)在不需要在顯示的指定這兩個(gè)屬性了论衍,可以直接平鋪傳遞
h('div',{
//props:{
// xxx:'spp'
//}
xxx:'spp'
})
2-事件綁定
現(xiàn)在也已經(jīng)剔除了on
屬性,而需要改成onEventType
的形式
h('div',{
//on:{
// click:()=>{}
//}
onClick(){}
})
3-slots
插槽需要改成函數(shù)形式
h('div',{
//slot:'slotName'
slotName(){}
})
4-組件渲染
現(xiàn)在使用h
函數(shù)渲染組件有兩種方式聚磺,筆者采用的方式如下
首先將要導(dǎo)入的組件掛載到全局
import { LoadingBar } from 'view-ui-plus';
app.component('LoadingBar', LoadingBar);
然后借助vue3
提供的resolveComponent
來執(zhí)行導(dǎo)入
import { resolveComponent } from 'vue';
h(resolveComponent('LoadingBar'))
- $set
基于proxy
的v3
已經(jīng)不需要$se
t或$delete
了坯台,現(xiàn)在直接進(jìn)行修改即可
- slot
v3
中的插件必須使用template
,且寫法有變更瘫寝,如下蜒蕾,現(xiàn)在可以合寫成一個(gè)了
<template
//slot="createTime"
//slot-scope="scope"
v-slot="scope"
>
...
</template>
- 三方組件庫(kù)
這種每個(gè)人的不一樣,筆者就不貼了焕阿,只說下筆者的處理方式
1-找vue3
版本
有些組件庫(kù)是支持vue3
版本的咪啡,這部分直接yarn
即可
2-修改源碼
如果沒有v3
版本,就利用patch-pkg
自己修改源碼做兼容
3-copy源碼
對(duì)于比較復(fù)雜的庫(kù)暮屡,筆者是找到其源代碼copy
了一份撤摸,然后在項(xiàng)目中引入并修改
- vue-loader
項(xiàng)目啟動(dòng)后,頁(yè)面元素之間的間隙會(huì)失效褒纲,配置如下
測(cè)試問題修復(fù)
- vue-router傳參
4.x
已經(jīng)不支持傳遞對(duì)象形式了
因?yàn)槟玫降氖亲址家模⑶疫@個(gè)字符串還沒法通過JSON.parse
進(jìn)行解析
但是又沒法一個(gè)一個(gè)調(diào)整莺掠,畢竟有一百多處都在使用
所以還是老規(guī)矩衫嵌,從源碼修改汁蝶。這只需要分兩步
第一步,重寫push
第二步,重寫query
屬性