vue語法自動轉(zhuǎn)typescript(解放雙手)
代碼的復(fù)用是一件很常見的事情,如果是公共代碼的復(fù)用那還好說,直接做成一個內(nèi)部私有庫,想用的話安裝一下 npm包就行了,但是業(yè)務(wù)代碼的復(fù)用就不好做成包了,一般都是復(fù)制粘貼
我一般寫代碼的時候,如果覺得某段業(yè)務(wù)代碼以前見過其他人寫過,那么考慮到業(yè)務(wù)優(yōu)先性,只要別人的代碼不是寫得太爛,我一般會優(yōu)先抄別人的代碼,省得自己再寫一遍
然后我就遇到了一個問題,公司目前前端項目大部分都是 vue,早期沒有 ts這個說法,后來新項目才逐漸引入 ts,所以新項目用的是 vue-ts,而一般想抄的老代碼都是沒有引入 ts的,固然,這二者是可以兼容存在的,但對于有著輕微代碼潔癖的我來說,還是不想看到同一個項目代碼里摻雜著 ts和非 ts兩種寫法的,所以只要有時間,我都會盡量手動把老代碼轉(zhuǎn)化為 ts規(guī)范的
難度倒是沒多少,只不過每一份都要手動轉(zhuǎn)一遍,轉(zhuǎn)得多了我忽然陷入沉思,我好像 repeat myself了啊,不太能忍,于是決定寫一個自動將 vue-js轉(zhuǎn)成 vue-ts的工具
這個工具的代碼已經(jīng)被我放到 github 上了,并且為了方便使用,我已經(jīng)將其做成了一個 npm 包,感興趣的可以親自試一下
@babel
涉及到 js語法轉(zhuǎn)換的東西,第一時間想到的就是 babel了,babel早就提供了豐富完善的 js語法的解析與反解析工具
@babel/parser
@babel/parser 是負責(zé)解析 js語法的工具,可以理解為將 js語法轉(zhuǎn)化為 ast,方便開發(fā)者進行自定義處理,通過 plugins來支持多種 js語法,例如 es6、es7、ts、flow、jsx甚至是一些實驗室的語法(experimental language proposals)等
例如:
const code = 'const a = 1'
const ast = require("@babel/parser").parse(code)
轉(zhuǎn)換后的 ast就是一個對象,數(shù)據(jù)結(jié)構(gòu)描述的就是 const a = 1這個表達式

對這個 ast進行遍歷,就可以獲得所有當(dāng)前解析的 js語法的信息,自然也能對其進行修改
@babel/generator
有解析就有反解析, @babel/generator用于將@babel/parser解析出的 ast轉(zhuǎn)化回字符串形式的 js代碼
const code = 'const a = 1;'
const ast = require("@babel/parser").parse(code)
const codeStr = require('@babel/generator').default(ast).code
code === codeStr // => true
其他
一般 @babel/parser、@babel/generator 和 @babel/traverse會一起出現(xiàn)使用,前兩個前面已經(jīng)介紹過了,至于 @babel/traverse,其主要作用就是對 @babel/parser生成的 ast進行遍歷,提供了一些方法,省得開發(fā)者自己去做各種判斷
不過我這里寫的這個程序,因為不需要太過細致的解析,所以沒用@babel/traverse這個東西,我按照自己的意愿對 ast進行遍歷操作
除此之外,babel還提供了一些其他的工具庫啦幫助庫啦,一般都不太用得到,想要詳細了解的可以自己去看文檔
本文下面所說的操作,基本上都是在 @babel/parser 轉(zhuǎn)換后的 ast,以及 @babel/generator 解析后的代碼字符串上進行的
props
vue官網(wǎng)對于 props的介紹在props
因此 props的以下幾種寫法都是符合規(guī)范的:
export default {
props: ['size', 'myMessage'],
props: {
a: Number,
b: [Number, String],
c: 'defaultValue',
d: {
type: [Number, String]
}
e: {
type: Number,
default: 0,
required: true,
validator: function (value) {
return value >= 0
}
}
}
}
上述轉(zhuǎn)換為 ts對應(yīng)如下:
export default class YourComponent extends Vue {
@Prop() readonly size: any | undefined
@Prop() readonly myMessage: any | undefined
@Prop({ type: Number }) readonly a: number | undefined
@Prop([Number, String]) readonly b: number | string | undefined
@Prop() readonly c!: any
@Prop({ type: [Number, String] }) readonly d: number | string | undefined
@Prop({ type: Number, default: 0, required: true, validator: function (value) {
return value >= 0
} }) readonly e!: number
}
ok,那就好辦了,首先 props值的類型只有 Array<string> 和 對象 這兩種類型
數(shù)組類型
Array<string>類型很好辦,就一個轉(zhuǎn)換模板:
@Prop() readonly propsName: any | undefined
只需要遍歷 Array<string>類型的 props,然后,把 propsName替換成真正的值即可
對象類型
對象類型的轉(zhuǎn)化模板在數(shù)組類型的模板上,多加了一些字符串,主要就是 @Prop的參數(shù):
@Prop({ type: typeV, default: defaultV, required: requiredV, validator: validatorV }) readonly propsName: typeV
props 這個大對象的每個屬性,都是一個 propsName,這個是確定的,然后 propsName對應(yīng)的值,可能是 type,type 分為單類型(例如 Number),以及類型數(shù)組(例如 [Number, String]);可能是一個對象,這個對象下的屬性最少為 0個,最多為 4個,如果這個對象存在一個屬性名為 type的屬性,則這個屬性的值也需要判斷單類型和類型數(shù)組,其他屬性直接取原值即可
無論 props對象的屬性值是對象還是 type,都需要處理 type,所以一個專門處理 type的方法 handlerType
如此一來,如果是 type,則 handlerType直接處理好;如果是對象,則遍歷這個對象的屬性,發(fā)現(xiàn)屬性是 type,則調(diào)用
handlerType進行處理,否則直接原樣作為 @Prop的參數(shù)即可
data
vue官網(wǎng)對于 data的介紹在data
data的類型可以是 Object 或 Function,即以下幾種寫法都合法:
export default {
data: {
a: 1
},
data () {
return {
a: 1
}
},
data: function () {
return {
a: 1
}
}
}
上述轉(zhuǎn)換為 ts對應(yīng)如下:
export default class YourComponent extends Vue {
a: number = 1
}
所以這里就很明了了,就是取 data返回值對象的每個屬性,作為 class的屬性,好像轉(zhuǎn)換一下就行了
但是,data其實還可以這么寫:
export default {
data () {
const originA = 1
return {
a: originA
}
}
}
當(dāng) data是 Function 類型時,在 return之前,還可以運行一段代碼,這段代碼的運行結(jié)果可能影響到 data的值
這種寫法并不少見,所以不可忽視,但如何處理 return之前的代碼?
我的做法是將 return之前的代碼放到 created生命周期函數(shù)中,并且在 created中的這些代碼之后,再對每個 data重新賦一遍值
比如,對于上面的代碼來說,轉(zhuǎn)換成 ts,可以這么做:
export default class YourComponent extends Vue {
a: any = null
created () {
const originA = 1
this.a = originA
}
}
所以,這就又涉及到 data對 created的數(shù)據(jù)修改了,這里可以考慮強制先處理 data,但是我看了下,其實這里寫兩段邏輯也并不復(fù)雜,所以我就不嚴格規(guī)定處理的順序了
model
vue官網(wǎng)對于 model的介紹在 model
model中引用了 props中的值,所以 model的使用其實是需要 props配合的
export default {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: {
type: Boolean
}
}
}
上述轉(zhuǎn)換為 ts對應(yīng)如下:
export default class YourComponent extends Vue {
@Model('change', { type: Boolean }) readonly checked!: boolean
}
可見,@Model是具備聲明 props的功能的,在 @Model中聲明了的 props,就沒必要在 @Prop中再聲明一遍了,所以我這里安排了一下處理順序,先處理 model,再處理 props,并且在處理 props的時候,將 model中已經(jīng)聲明了的 props篩選掉
當(dāng)然,你也可以不專門先處理 model再處理 props,只要在處理 model的時候判斷一下,是否在此之前已經(jīng)處理過 props了,根據(jù)結(jié)果來做相應(yīng)的處理流程,但這樣未免有些麻煩,需要根據(jù) props的處理與否來寫兩段邏輯,這兩段邏輯比上面 data影響 created的要復(fù)雜一些,所以這里我就直接按照順序處理了,省得給自己找麻煩
computed
vue官網(wǎng)對于 model的介紹在 computed
以下幾種 computed的寫法都是正確的
export default {
computed: {
a () { return true },
b: function () { return true },
d: {
get () { return true },
set: function (v) { console.log(v) }
}
}
}
vue-property-decorator并沒有提供專門的用于 computed的修飾器,因為 ES6的 get/set語法本身就可以替代 computed
上述轉(zhuǎn)換為 ts對應(yīng)如下:
export default class YourComponent extends Vue {
get a () { return true }
get b () { return true },
get d (){ return true },
set d (v) { console.log(v) }
}
除此之外,computed其實還支持箭頭函數(shù)的寫法:
export default {
computed: {
e: () => { return true }
}
}
但是 class語法的 get/set不支持箭頭函數(shù),所以不好轉(zhuǎn)換,另外因為箭頭函數(shù)會改變 this的指向,而 computed計算的就是當(dāng)前 vue實例上的屬性,所以一般也不推薦在 computed中使用箭頭函數(shù),固然你可以在箭頭函數(shù)的第一個參數(shù)上獲得當(dāng)前 vue實例,但這就未免有點多此一舉的嫌疑了,所以我這里略過對箭頭函數(shù)的處理,只會在遇到 computed上的箭頭函數(shù)時,給你一個提示
watch
vue官網(wǎng)對于 watch的介紹在watch
以下都是合法的 watch寫法:
export default {
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// 方法名
b: 'someMethod',
// 該回調(diào)會在任何被偵聽的對象的 property 改變時被調(diào)用,不論其被嵌套多深
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
// 該回調(diào)將會在偵聽開始之后被立即調(diào)用
d: {
handler: 'someMethod',
immediate: true
},
e: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
immediate: true
}
],
// watch vm.e.f's value: {g: 5}
'e.f': function (val, oldVal) { /* ... */ }
}
}
上述轉(zhuǎn)換為 ts對應(yīng)如下:
export default class YourComponent extends Vue {
@Watch('a')
onAChanged(val: any, oldVal: any) {}
@Watch('b')
onBChanged (val: any, oldVal: any) {
this.someMethod(val, oldVal)
}
@Watch('c', { deep: true })
onCChanged (val: any, oldVal: any) {}
@Watch('d', { deep: true })
onDChanged (val: any, oldVal: any) {}
@Watch('e')
onE1Changed (val: any, oldVal: any) {}
@Watch('e')
onE2Changed (val: any, oldVal: any) {}
@Watch('e', { immediate: true })
onE3Changed (val: any, oldVal: any) {}
@Watch('e.f')
onEFChanged (val: any, oldVal: any) {}
}
寫法還是很多的,所以判斷分支肯定少不了
watch下的每個屬性都是一個需要進行 watch的 vue響應(yīng)值,這些屬性的值可以是字符串、函數(shù)、對象和數(shù)組,共四種類型
其中,字符串類型就是相當(dāng)于調(diào)用當(dāng)前 vue實例里的方法,函數(shù)類型就是調(diào)用這個函數(shù),比較簡單;
對于對象類型,其具有三個屬性:handler、deep、immediate,三個屬性都是可選,其中 handler的值是函數(shù)或字符串,其他兩個屬性的值都是 boolean類型;
對于數(shù)組類型,其每一個數(shù)組項,其實都相當(dāng)于是字符串類型、函數(shù)類型和對象類型的聚合,所以實際上只要處理這三種類型即可,數(shù)組類型則直接遍歷數(shù)組項,每個數(shù)組項的類型肯定在這三個類型之內(nèi),按照類型調(diào)用相應(yīng)的處理方法即可。
這是主體部分,除此之外,還需要考慮 handler函數(shù)的形式,以下幾種函數(shù)的寫法都是合法的:
export default {
watch: {
a: function {},
b () {},
c: () => {},
d: async function {},
e: async () => {}
}
}
不僅在 watch里面,其他一些 vue實例屬性,比如 created、computed等,只要是可能出現(xiàn)函數(shù)的地方,都需要考慮到這些寫法
當(dāng)然,除此之外,還有 Generator函數(shù),但我這里不考慮,有更好的 async/await可用,為什么非要用 Generator
methods
vue實例的方法,都作為 methods這個對象的屬性存在,每個方法都是一個函數(shù),所以只需要將原 methods下的所有方法取出,轉(zhuǎn)換為 class的方法即可,沒什么工作量
不過需要注意的是,函數(shù)的寫法有很多,還可以支持 async/await,這些寫法都需要考慮到
lifeCycle
vue的生命周期鉤子函數(shù)有很多,還有一些第三方的鉤子函數(shù),例如 vue-router:
const vueLifeCycle = ['beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'activated', 'deactivated', 'beforeDestroy', 'destroyed', 'errorCaptured', 'beforeRouteEnter', 'beforeRouteUpdate', 'beforeRouteLeave']
這些鉤子函數(shù)其實就是函數(shù),跟 methods的處理方法一樣
component
這個比較簡單,轉(zhuǎn)化一下然后拼接
export default {
components: {
a: A,
B
},
}
上述轉(zhuǎn)換為 ts對應(yīng)如下:
@Component({
components: {
a: A,
B
}
})
export default class TransVue2TS extends Vue {}
所以就是把原 components的屬性全部映射一遍即可
mixins
vue官網(wǎng)對于 mixins的介紹在 mixins
其值類型為 Array<Object>
export default {
mixins: [A, B]
}
上述轉(zhuǎn)換為 ts對應(yīng)如下:
export default class TransVue2TS extends Mixins(A, B) {}
原本 extends Vue改成 extends Mixins,并且 Mixins的參數(shù)就是原 mixins的所有數(shù)組項
provide && inject
當(dāng)我考慮如何處理這兩個的時候,看了下 vue官網(wǎng),官網(wǎng)上對于這兩個是這么說的:
provide 和 inject 主要為高階插件/組件庫提供用例。并不推薦直接用于應(yīng)用程序代碼中。
并且在這段話上,還專門用紅色感嘆號標識了一下,說白了就是不建議你在業(yè)務(wù)代碼中,因為這不利于數(shù)據(jù)的追蹤,完全可以使用成熟的 vueBus或者 vuex代替,一般也不會用到這個東西的,我寫這個轉(zhuǎn)換程序也是為了轉(zhuǎn)換業(yè)務(wù)代碼,所以我沒有對這兩個做處理,如果發(fā)現(xiàn)代碼中存在這兩個屬性,會提示你自己手動處理
emit && ref
這兩個都只是一種類似語法糖的東西,可以不做處理
文件處理
上述是針對一份 .vue文件的詳細處理的邏輯,想要真正的接入實際文件乃至文件夾的處理,自然少不了文件的讀取和更新操作,這就涉及到 node的文件處理內(nèi)容了,不過并不復(fù)雜,就不多說了
npm 包
代碼寫完之后,為了簡化使用流程,我將其打包成了一個 npm包上傳到 npm上去了,想要使用的話,只需要下載這個包,然后在命令行中輸入指令即可
npm i transvue2ts -g
安裝完之后,默認是跟 vue-cli一樣,會把此庫的路徑寫到系統(tǒng)的 path中,直接打開命令行工具即可使用,同時支持單文件和文件目錄耳朵轉(zhuǎn)化transvue2ts是庫的指令,第二個參數(shù)是需要處理的文件(夾)的 完整全路徑例如:處理 E:\project\testA\src\test.vue文件:
transvue2ts E:\project\testA\src\test.vue => 輸出路徑:E:\project\testA\src\testTs.vue
處理 E:\project\testA\src文件夾下的所有 .vue文件:
transvue2ts E:\project\testA\src => 輸出路徑:E:\project\testA\srcTs
對于單文件來說,其必須是 .vue結(jié)尾,轉(zhuǎn)化后的文件將輸出到同級目錄下,文件名為原文件名 + Ts,例如 index.vue => indexTs.vue;對于文件目錄來說,程序?qū)Υ宋募夸涍M行遞歸遍歷,找出這個文件夾下所有的 .vue文件進行轉(zhuǎn)化,轉(zhuǎn)化后的文件將按照原先的目錄結(jié)構(gòu)全部平移到同級目錄下的一個新文件夾中,例如 /src => /srcTs
總結(jié)
這個轉(zhuǎn)化程序看起來很麻煩的樣子,概括一下,其實就三步:
- 列舉所有需要進行轉(zhuǎn)化的 vue-js語法及其多變的寫法
- 列舉 js-ts語法之間的轉(zhuǎn)化映射關(guān)系
- 寫語法轉(zhuǎn)化代碼
本質(zhì)上這個程序就是一個翻譯器,將 vue-js語法翻譯成 vue-ts語法,難點在于你要找到二者之間所有語法的映射關(guān)系,并知道如何進行處理,所以實際上大部分都是體力活
只要你明白了這其中的套路,其實換個什么 vue 轉(zhuǎn) wepy,或者 react轉(zhuǎn)微信小程序,其實都是一樣,都是翻譯器,都是體力活,只不過有些很輕松,也就是搬幾塊磚的事情,而有些體力活比較辛苦還需要動腦子罷了
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue3結(jié)合TypeScript項目開發(fā)實踐總結(jié)
本文主要介紹了Vue3結(jié)合TypeScript項目開發(fā)實踐總結(jié),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09
vue 如何從單頁應(yīng)用改造成多頁應(yīng)用
這篇文章主要介紹了vue 如何從單頁應(yīng)用改造成多頁應(yīng)用,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下2020-10-10
Vue3 組合式函數(shù)Composable最佳實戰(zhàn)
這篇文章主要為大家介紹了Vue3 組合式函數(shù)Composable最佳實戰(zhàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06

