原有vue專案接入typescript
**摘要: **TypeScript是王道。
- 原文:原有vue專案接入typescript
- 作者:陳龍
- 公眾號:大轉轉FE
Fundebug經授權轉載,版權歸原作者所有。
為什麼要接入typescript
javascript由於自身的弱型別,使用起來非常靈活。
這也就為大型專案、多人協作開發埋下了很多隱患。如果是自己的私有業務倒無所謂,主要是對外介面和公共方法,對接起來非常頭疼。主要表現在幾方面:
- 引數型別沒有校驗,怎麼傳都有,有時會出現一些由於型別轉換帶來的未知問題。
- 介面文件不規範,每次都要通過讀程式碼才能知道傳什麼,怎麼傳
- 介面編寫符合規範,但是公共庫中有大量的處理型別校驗的程式碼
這就非常不利於工程標準化。於是我們決定引入typescript進行程式碼層面的強校驗。
概覽
原有vue專案接入ts主要包含下面幾大步驟:
- 安裝typescript相關npm包
- 修改webpack和ts配置檔案
- 專案公共庫和vue檔案改造
ok,我們開始
1. 安裝typescript相關npm包
這塊有個非常重要的點需要注意:
就是要根據你本地的環境,去升級對應版本的typescript
這塊是很多初次使用的同學都會遇到的問題。
因為只是看到了官網的教程,一步一步安裝完發現各種報錯。主要問題就是webpack版本不匹配,或者其他一些npm包版本不匹配
以我本地為例:
我本地環境是webpack3,所以直接安裝最新版本的typescript,控制檯會報錯webpack版本過低的問題。
所以你要不把自己的webpack升級到webapck4.要不就採用與之相匹配的typescript版本。
我選擇的是後者,因為直接給自己的專案升級到webapck4,會花費更長的時間。我們用的腳手架是公司內部統一的。裡面集成了很多底層通用的基礎服務。冒然升級webpack4會帶來更大的麻煩,更何況專案時間比較緊迫,你懂得。
下面是我安裝的包和對應的版本:
- "typescript": "^3.1.4" (這個是必須的,ts庫)
- "ts-loader": "^3.5.0" (識別ts的laoder)
- "tslint": "^5.11.0" (tslint校驗庫)
- "tslint-loader": "^3.5.4" (tslint的loader)
- "tslint-config-standard": "^8.0.1" (用於tslint預設校驗規則)
- "vue-property-decorator": "^7.2.0" (用於在.vue檔案中使用ts語法)
2. 修改webpack和ts配置檔案
修改webpack配置檔案(加入ts的相關配)
base: {
entry: {
...
app: resolve('src/main.ts') // 把main.js改為main.ts
}
...
resolve: {
...
extensions: ['vue', '.js', '.ts']
}
module: {
rules: [
...,
{ // 加入對檔案的ts識別
test: /\.ts$/,
exclude: /node_modules/,
enforce: 'pre',
loader: 'tslint-loader'
}, {
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
}
]
}
注意: main.js改成main.ts後,還要做一些改造,這個比較簡單,按照tslint的錯誤提示改就可以了
- 在根目錄下建立tslint.json(類似eslint,這裡設定一個校驗標準)
{
"extends": "tslint-config-standard",
"globals": {
"require": true
}
}
在根目錄建立tsconfig.json(typescript配置檔案)
{
"compilerOptions": {
// 編譯目標平臺
"target": "es5",
// 輸出目錄
"outDir": "./dist/",
// 新增需要的解析的語法,否則TS會檢測出錯。
"lib": ["es2015", "es2016", "dom"],
// 模組的解析
"moduleResolution": "node",
// 指定生成哪個模組系統程式碼
"module": "esnext",
// 在表示式和宣告上有隱含的any型別時報錯
"noImplicitAny": false,
// 把 ts 檔案編譯成 js 檔案的時候,同時生成對應的 map 檔案
"sourceMap": true,
// 允許編譯javascript檔案
"allowJs": true,
// 指定基礎目錄
"baseUrl": "./",
// 啟用裝飾器
"experimentalDecorators": true,
// 移除註釋
"removeComments": true,
"pretty": true,
// 是相對於"baseUrl"進行解析
"paths": {
"vue": ["node_modules/vue/types"],
"@/*": ["src/*"]
}
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}
在src目錄下建立sfc.d.ts(用來宣告全域性變數、class、module、function、名稱空間)
我們在這裡主要是讓ts識別.vue檔案、window物件和一些module
具體declare的使用方式請看這裡
/**
* 告訴 TypeScript *.vue 字尾的檔案可以交給 vue 模組來處理
* 而在程式碼中匯入 *.vue 檔案的時候,需要寫上 .vue 字尾。
* 原因還是因為 TypeScript 預設只識別 *.ts 檔案,不識別 *.vue 檔案
*/
declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
/**
* 告訴 TypeScript window是個全域性物件,直接可用,這樣就不會在window.xx = 123時報錯
*/
declare var window: any
/**
* 引入部分第三方庫/自己編寫的模組的時候需要額外宣告檔案
* 引入的時候,需要使用類似 import VueLazyLaod from 'vue-lazyload' 的寫法
*/
declare module 'vue-lazyload'
declare module '@zz/perf/vue'
declare module 'raven-js'
declare module 'raven-js/plugins/vue'
將src/main.js改為main.ts
3. 專案公共庫和vue檔案改造
這個部分是最麻煩的,主要有幾大塊
基礎庫改造
如果你的基礎庫引用了大量的npm包,那麼恭喜你,這部分你的改造成本會低很多。
如果你的lib庫有相當一部分都是自己手寫的,那麼,我也得恭喜你。。。
我們自己的lib庫裡,有大量的自己維護的js檔案。那麼如果你要進行ts改造的話,通通都要改。
舉個例子: lib/url.js中的getParam (演算法並不高階,就是易讀、相容性好)
export default class URL{
/**
* @memberOf URL
* @summary 獲取當前頁面連線中指定引數
* @type {function}
* @param {string} param1 - 如果param2為undefined,param1是指從當前頁面url中獲取指定引數的key, 如果param2不為空,param1為指定的url
* @param {string} param2 - 可選引數,如果param2存在,則從指定的param1連線中獲取對應引數的key
* @return {string|null}
*/
static getParam (param1, param2) {
let url = ''
let param = null;
// 如果只有一個引數,預設從當前頁面連結獲取引數
if (typeof param2 === 'undefined') {
url = window && window.location.href || ''
param = param1
// 從指定url中獲取引數
} else {
url = param1
param = param2
}
// 排除hash的影響
url = url.split('#')[0]
if (url.indexOf('?') > -1) {
url = url.split('?')[1]
}
const reg = new RegExp('(^|&)' + param + '=([^&]*)[&#$]*', 'i')
const rstArr = url.match(reg)
if (rstArr !== null) {
return decodeURIComponent(rstArr[2])
}
return null
}
...
}
改造後的檔案為:lib/url.ts
export default class URL {
/**
* @memberOf URL
* @summary 獲取url中指定引數
* @type {function}
* @param {string} param1 - 如果param2為undefined,param1是指從當前頁面url中獲取指定引數的key, 如果param2不為空,param1為指定的url
* @param {string} param2 - 可選引數,如果param2存在,則從指定的param1連線中獲取對應引數的key
* @return {string|null}
*/
static getParam (param1: string, param2?: string): string {
let url: string = ''
let param = null
// 如果只有一個引數,預設從當前頁面連結獲取引數
if (typeof param2 === 'undefined') {
url = window && window.location.href || ''
param = param1
// 從指定url中獲取引數
} else {
url = param1
param = param2
}
url = url.split('#')[0]
if (url.indexOf('?') > -1) {
url = url.split('?')[1]
}
const reg = new RegExp('(^|&)' + param + '=([^&]*)[&#$]*', 'i')
const rstArr = url.match(reg)
if (rstArr !== null) {
return decodeURIComponent(rstArr[2])
}
return null
}
...
}
對於一個方法多種呼叫方式,如果你想完全改成typescript推薦的方式,你可以用到方法過載。
我沒有用是因為我不希望改變原有頁面的使用方式。
注:對於一個大型專案來講,我們並不建議上來就對全部的檔案進行ts改造。
我們更建議採用漸進式改造方案,在不影響原有頁面的情況下,逐一改造。具體方案後面會介紹
vue檔案改造
src/components/helper/newUser/index.vue
<template>...</template>
<script>
import { LEGO_ATTR, initLegoData, legic } from '@/lib/legic'
import { getMyProfile } from '@/api/helper'
import { toast } from '@/lib/ZZSDK'
import myComponent from './myComponent.vue'
let flag = false // 是否傳送視訊點選埋點
export default {
components: {
// 自定義元件
myComponent
},
data () {
return {
// 使用者頭像
portrait: '',
// 使用者名稱稱
nickName: '',
// 是否點選播放
isPlay: false
}
},
mounted () {
this.initData()
initLegoData({
type: 'newUserGuide'
});
legic(LEGO_ATTR.newUserGuide.SHOW);
},
methods: {
initData () {
getMyProfile().then(data => {
console.log('data', data)
const { respData } = data
this.portrait = respData.portrait || ''
this.nickName = respData.nickname || ''
}).catch(err => {
toast({ msg: err })
})
},
goPageClick (type) {
switch (type) {
case 'SUN':
legic(LEGO_ATTR.newUserGuide.SUNVILLAGECLICK)
break
case 'FOOTBALL':
legic(LEGO_ATTR.newUserGuide.FOOTBALLCLICK)
break
case 'SIGN':
legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
break
default:
return
}
},
videoClick () {
if (flag) {
return
} else {
flag = true
legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
this.isPlay = true
this.$refs.video.play()
}
}
}
}
</script>
<style lang="scss" scoped>...</style>
改造後
<template>...</template>
<script lang="ts">
import { LEGO_ATTR, initLegoData, legic } from '@/lib/legic'
import { getMyProfile } from '@/api/helper.ts'
import { toast } from '@/lib/ZZSDK'
import { Component, Vue } from 'vue-property-decorator'
import test from './test.vue'
let flag: boolean = false // 是否傳送視訊點選埋點
@Component({
components: {
test
}
})
export default class NewUser extends Vue {
// 使用者頭像
portrait = ''
// 使用者名稱稱
nickName = ''
// 是否點選播放
isPlay = false
mounted (): void {
this.initData()
initLegoData({
type: 'newUserGuide'
});
legic(LEGO_ATTR.newUserGuide.SHOW)
}
initData () {
// 獲取profile資訊
getMyProfile().then((data: any) => {
console.log('data', data)
const { respData } = data
this.portrait = respData.portrait || ''
this.nickName = respData.nickname || ''
}).catch((err: string) => {
toast({ msg: err })
})
}
goPageClick (type: string) {
switch (type) {
case 'SUN':
legic(LEGO_ATTR.newUserGuide.SUNVILLAGECLICK)
break
case 'FOOTBALL':
legic(LEGO_ATTR.newUserGuide.FOOTBALLCLICK)
break
case 'SIGN':
legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
break
default:
return
}
}
videoClick () {
if (flag) {
return
} else {
flag = true
legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
this.isPlay = true
this.$refs.video['play']()
}
}
}
</script>
<style lang="scss" scoped>...</style>
myComponent.vue改造前略,這裡只展示改造後的元件
<template>
<div class="main">{{title}}{{name}}</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class MyComponent extends Vue {
@Prop({ type: String, default: '' })
name: string
title: string = '您好'
}
</script>
<style lang="scss" scoped>
.main{
display: none;
}
</style>
這裡需要注意的是:
- ts預設不會識別.vue檔案,所以需要在sfc.d.ts檔案中宣告,同時在引入vue元件時,要加.vue字尾
- 引入vue-property-decorator外掛。採用修飾符的方式進行元件註冊,這樣裡面的data、prop和function都通過扁平化方式呼叫(這也是官方推薦的方式)
- ts中import引入檔案,如果不寫字尾,預設是js檔案。如果js檔案沒有,則才識別ts檔案
現在說下前面提到的改造方案:
這裡其實主要涉及.vue檔案和lib庫的改造,vue檔案沒啥可說的,一個個改就可以了。主要說lib裡面的檔案,這裡我建議:
- 一開始保留原來的js檔案,並不刪除。這樣目前尚未改造的檔案可以繼續使用
- 新建對應的ts檔案,比如lib中有util.js,新建立util.ts
- 新改造的vue檔案通通引入lib庫中xx.ts(要加.ts字尾),如import Util from '@/lib/util.ts' 這樣可以一點點改造整個專案,同時未改造的頁面照樣可以執行。
ok以上就是我們改造的全部過程。 有什麼問題可以指正,大家互相學習。
關於Fundebug
Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了9億+錯誤事件,得到了Google、360、金山軟體、百姓網等眾多知名使用者的認可。歡迎免費試用!