摘要: Typescript是王道。
- 原文:原有vue项目接入typescript
- 作者:陈龙
- 公众号:大转转FE
为什么要接入typescriptjavascript由于自身的弱类型,使用起来非常灵活。
这也就为大型项目、多人协作开发埋下了很多隐患。如果是自己的私有业务倒无所谓,主要是对外接口和公共方法,对接起来非常头疼。主要表现在几方面:
- 参数类型没有校验,怎么传都有,有时会出现一些由于类型转换带来的未知问题。
- 接口文档不规范,每次都要通过读代码才能知道传什么,怎么传
- 接口编写符合规范,但是公共库中有大量的处理类型校验的代码
于是我们决定引入typescript进行代码层面的强校验。概览原有vue项目接入ts主要包含下面几大步骤:
- 安装typescript相关npm包
- 修改webpack和ts配置文件
- 项目公共库和vue文件改造
就是要根据你本地的环境,去升级对应版本的typescript这块是很多初次使用的同学都会遇到的问题。因为只是看到了官网的教程,一步一步安装完发现各种报错。
主要问题就是webpack版本不匹配,或者其他一些npm包版本不匹配以我本地为例:我本地环境是webpack3,所以直接安装最新版本的typescript,控制台会报错webpack版本过低的问题。所以你要不把自己的webpack升级到webapck4.要不就采用与之相匹配的typescript版本。
我选择的是后者,因为直接给自己的项目升级到webapck4,会花费更长的时间。
我们用的脚手架是公司内部统一的。里面集成了很多底层通用的基础服务。冒然升级webpack4会带来更大的麻烦,更何况项目时间比较紧迫,你懂得。
下面是我安装的包和对应的版本:
- “typescript”: “^3.1.4”
- “ts-loader”: “^3.5.0”
- “tslint”: “^5.11.0”
- “tslint-loader”: “^3.5.4”
- “tslint-config-standard”: “^8.0.1”
- “vue-property-decorator”: “^7.2.0”
base
:
{
entry
:
{
...
app
:
resolve
// 把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"
],
"@*"
],
"exclude"
:
[
"node_modules"
]
}
在src目录下创建sfc.d.ts(用来声明全局变量、class、module、function、命名空间)我们在这里主要是让ts识别.vue文件、window对象和一些module具体declare的使用方式请看这里declare
module
"*.vue"
{
import
Vue
from
'vue'
export
default
Vue
}
declare
var
window
:
any
declare
module
'vue-lazyload'
declare
module
'@zz/perf/vue'
declare
module
'raven-js'
declare
module
'raven-js/plugins/vue'
将src/main.js改为main.ts3. 项目公共库和vue文件改造这个部分是最麻烦的,主要有几大块基础库改造如果你的基础库引用了大量的npm包,那么恭喜你,这部分你的改造成本会低很多。如果你的lib库有相当一部分都是自己手写的,那么,我也得恭喜你。我们自己的lib库里,有大量的自己维护的js文件。那么如果你要进行ts改造的话,通通都要改。举个例子: lib/url.js中的getParam export default class URL { static getParam { let url = '' let param = null ; // 如果只有一个参数,默认从当前页面链接获取参数 if { url = window && window . location . href || '' param = param1 // 从指定url中获取参数 } else { url = param1 param = param2 } // 排除hash的影响 url = url . split [ 0 ] if > - 1 ) { url = url . split [ 1 ] } const reg = new RegExp ' + param + '=[$]*' , 'i' ) const rstArr = url . match if { return decodeURIComponent } return null } ... } export default class URL { static getParam : string { let url string = '' let param = null // 如果只有一个参数,默认从当前页面链接获取参数 if { url = window && window . location . href || '' param = param1 // 从指定url中获取参数 } else { url = param1 param = param2 } url = url . split [ 0 ] if > - 1 ) { url = url . split [ 1 ] } const reg = new RegExp ' + param + '=[$]*' , 'i' ) const rstArr = url . match if { return decodeURIComponent } return null } ... }
改造后的文件为:lib/url.ts
对于一个方法多种调用方式,如果你想完全改成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
;
legic
;
},
methods
:
{
initData
{
getMyProfile
.
then
const
{
respData
}
=
data
this
.
portrait
=
respData
.
portrait
||
''
this
.
nickName
=
respData
.
nickname
||
''
}).
catch
})
},
goPageClick
{
switch
{
case
'SUN'
:
legic
break
case
'FOOTBALL'
:
legic
break
case
'SIGN'
:
legic
break
default
:
return
}
},
videoClick
{
if
{
return
}
else
{
flag
=
true
legic
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
export
default
class
NewUser
extends
Vue
{
// 用户头像
portrait
=
''
// 用户名称
nickName
=
''
// 是否点击播放
isPlay
=
false
mounted
:
void
{
this
.
initData
initLegoData
;
legic
}
initData
{
// 获取profile信息
getMyProfile
.
then
=>
{
console
.
log
const
{
respData
}
=
data
this
.
portrait
=
respData
.
portrait
||
''
this
.
nickName
=
respData
.
nickname
||
''
}).
catch
=>
{
toast
})
}
goPageClick
{
switch
{
case
'SUN'
:
legic
break
case
'FOOTBALL'
:
legic
break
case
'SIGN'
:
legic
break
default
:
return
}
}
videoClick
{
if
{
return
}
else
{
flag
=
true
legic
this
.
isPlay
=
true
this
.
$refs
.
video
[
'play'
]
}
}
}
script
>
<
style
lang
=
"scss"
scoped
>
...
style
>
myComponent.vue改造前略,这里只展示改造后的组件<
template
>
<
p
class
=
"main"
>
{{title}}{{name}}p
>
template
>
<
script
lang
=
"ts"
>
import
{
Component
,
Prop
,
Vue
}
from
'vue-property-decorator'
@
Component
export
default
class
MyComponent
extends
Vue
{
@
Prop
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文件
主要说lib里面的文件,这里我建议:
- 一开始保留原来的js文件,并不删除。这样目前尚未改造的文件可以继续使用
- 新建对应的ts文件,比如lib中有util.js,新创建util.ts
- 新改造的vue文件通通引入lib库中xx.ts(要加.ts后缀),如import Util from ‘@/lib/util.ts’ 这样可以一点点改造整个项目,同时未改造的页面照样可以运行。
有什么问题可以指正,大家互相学习。