javascript因爲自身的弱類型,使用起來很是靈活。javascript
這也就爲大型項目、多人協做開發埋下了不少隱患。若是是本身的私有業務倒無所謂,主要是對外接口和公共方法,對接起來很是頭疼。主要表如今幾方面:css
這就很是不利於工程標準化。因而咱們決定引入typescript進行代碼層面的強校驗。html
原有vue項目接入ts主要包含下面幾大步驟:vue
這塊有個很是重要的點須要注意:java
就是要根據你本地的環境,去升級對應版本的typescriptnode
這塊是不少初次使用的同窗都會遇到的問題。webpack
由於只是看到了官網的教程,一步一步安裝完發現各類報錯。主要問題就是webpack版本不匹配,或者其餘一些npm包版本不匹配web
我本地環境是webpack3,因此直接安裝最新版本的typescript,控制檯會報錯webpack版本太低的問題。算法
因此你要不把本身的webpack升級到webapck4.要不就採用與之相匹配的typescript版本。typescript
我選擇的是後者,由於直接給本身的項目升級到webapck4,會花費更長的時間。咱們用的腳手架是公司內部統一的。裏面集成了不少底層通用的基礎服務。冒然升級webpack4會帶來更大的麻煩,更況且項目時間比較緊迫,你懂得。
下面是我安裝的包和對應的版本:
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後,還要作一些改造,
main.ts
import Vue from 'vue'
import { legic } from '@/lib/legic.ts'
...
// 給vue對象添加自定義方法
declare module 'vue/types/vue' {
interface Vue {
$legic: Function
}
}
// 統計上報方法注入
Vue.prototype.$legic = legic
...
;(window as any).vm = new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
複製代碼
這裏有幾個注意的點
- 給Vue的prototpye注入新屬性和方法的話,直接用Vue.prototype.xxx = xxx ts校驗是不經過的。須要經過declare聲明一下
- 在main.ts中用window對象須要寫成(window as any)這種方式
{
"extends": "tslint-config-standard",
"globals": {
"require": true
}
}
複製代碼
{
"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"
]
}
複製代碼
咱們在這裏主要是讓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
這個部分是最麻煩的,主要有幾大塊
基礎庫改造
若是你的基礎庫引用了大量的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改造。
咱們更建議採用漸進式改造方案,在不影響原有頁面的狀況下,逐一改造。具體方案後面會介紹
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>
複製代碼
這裏須要注意的是:
如今說下前面提到的改造方案:
這裏其實主要涉及.vue文件和lib庫的改造,vue文件沒啥可說的,一個個改就能夠了。主要說lib裏面的文件,這裏我建議:
ok以上就是咱們改造的所有過程。 有什麼問題能夠指正,你們互相學習