前言 總所周知,Vue新版本3.0 使用 TypeScript 開發,讓本來就很火的 TypeScript 受到更多人的關註。雖然 TypeScript 在近幾年才火,但其實它誕生於2012年10月,正式版本發佈於2013年6月,是由微軟編寫的自由和開源的編程語言。TypeScript 是 Java ...
前言
總所周知,Vue新版本3.0 使用 TypeScript 開發,讓本來就很火的 TypeScript 受到更多人的關註。雖然 TypeScript 在近幾年才火,但其實它誕生於2012年10月,正式版本發佈於2013年6月,是由微軟編寫的自由和開源的編程語言。TypeScript 是 JavaScript 的一個超集,擴展了 JavaScript 的語法,添加了可選的靜態類型和基於類的面向對象編程。
JavaScript開發中經常遇到的錯誤就是變數或屬性不存在,然而這些都是低級錯誤,而靜態類型檢查恰好可以彌補這個缺點。什麼是靜態類型?舉個慄子:
//javascript
let str = 'hello'
str = 100 //ok
//typescript
let str:string = 'hello'
str = 100 //error: Type '100' is not assignable to type 'string'.
可以看到 TypeScript 在聲明變數時需要為變數添加類型,如果變數值和類型不一致則會拋出警告。靜態類型只在編譯時進行檢查,而且最終編譯出來的代碼依然是 JavaScript。即使我們為 string 類型的變數賦值為其他類型,代碼也是可以正常運行的。
其次,TypeScript 增加了代碼的可讀性和可維護性,類型定義實際上就是一個很好的文檔,比如在使用函數時,只需要看看參數和返回值的類型定義,就大概知道這個函數如何工作。
目錄
準備工作
npm
安裝 typescript
npm install typescript @vue/cli-plugin-typescript -D
新增文件
在項目的根目錄下創建 shims-vue.d.ts、shims-tsx.d.ts、tsconfig.json
- shims-vue.d.ts
import Vue from 'vue';
declare module '*.vue' {
export default Vue;
}
- shims-tsx.d.ts
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
type Element = VNode
type ElementClass = Vue
interface IntrinsicElements {
[elem: string]: any;
}
}
}
- tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators":true,
"sourceMap": true,
"noImplicitThis": false,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
ESLint配置
為什麼使用 ESLint 而不是 TSLint?
今年1月份,TypeScript官方發佈博客推薦使用ESLint來代替TSLint。而 ESLint 團隊將不再維護 typescript-eslint-parser
,也不會在 Npm 上發佈,任何使用 tyescript-eslint-parser
的用戶應該改用 @tyescript-eslint/parser
。
官方的解釋:
我們註意到TSLint規則的操作方式存在一些影響性能的體繫結構問題,ESLint已經擁有了我們希望從linter中得到的更高性能的體繫結構。此外,不同的用戶社區通常有針對ESLint而不是TSLint構建的lint規則(例如React hook或Vue的規則)。鑒於此,我們的編輯團隊將專註於利用ESLint,而不是複製工作。對於ESLint目前沒有覆蓋的場景(例如語義linting或程式範圍的linting),我們將致力於將ESLint的TypeScript支持與TSLint等同起來。
如何使用
AlloyTeam 提供了一套全面的EsLint配置規範,適用於 React/Vue/Typescript 項目,並且可以在此基礎上自定義規則。
GitHub
安裝
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy
配置項的說明查看AlloyTeam ESLint 規則
配置
在項目的根目錄中創建.eslintrc.js,然後將以下內容複製到其中:
module.exports = {
extends: [
'alloy',
'alloy/typescript',
],
env: {
browser: true,
node: true,
},
rules: {
// 自定義規則
'spaced-comment': 'off',
'@typescript-eslint/explicit-member-accessibility': 'off',
'grouped-accessor-pairs': 'off',
'no-constructor-return': 'off',
'no-dupe-else-if': 'off',
'no-import-assign': 'off',
'no-setter-return': 'off',
'prefer-regex-literals': 'off'
}
};
補充
如果想知道配置項更多使用,可以到ESLint官網搜索配置項。
如果使用的是VScode,推薦使用ESLint插件輔助開發。
文件改造
入口文件
- main.js 改為 main.ts
- vue.config.js 修改入口文件
const path = require('path')
module.exports = {
...
pages: {
index: {
entry: path.resolve(__dirname+'/src/main.ts')
},
},
...
}
vue組件文件
隨著TypeScript和ES6里引入了類,在一些場景下我們需要額外的特性來支持標註或修改類及其成員。 裝飾器(Decorators)為我們在類的聲明及成員上通過元編程語法添加標註提供了一種方式。
Vue 也為我們提供了類風格組件的 TypeScript 裝飾器,使用裝飾器前需要在 tsconfig.json 將 experimentalDecorators 設置為 true。
安裝vue裝飾器
vue-property-decorator
庫完全依賴vue-class-component
,在安裝時要一起裝上
npm install vue-class-component vue-property-decorator -D
改造.vue
只需要修改srcipt內的東西即可,其他不需要改動
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import draggable from 'vuedraggable'
@Component({
created(){
},
components:{
draggable
}
})
export default class MyComponent extends Vue {
/* data */
private ButtonGrounp:Array<any> = ['edit', 'del']
public dialogFormVisible:boolean = false
/*method*/
setDialogFormVisible(){
this.dialogFormVisible = false
}
addButton(btn:string){
this.ButtonGrounp.push(btn)
}
/*compute*/
get routeType(){
return this.$route.params.type
}
}
</script>
類成員修飾符,不添加修飾符則預設為public
- public:公有,可以自由訪問類的成員
- protected:保護,類及其繼承的子類可訪問
- private:私有,只有類可以訪問
Prop
!: 為屬性使用明確的賦值斷言修飾符,瞭解更多看文檔
import { Component, Vue, Prop } from "vue-property-decorator";
export default class MyComponent extends Vue {
...
@Prop({type: Number,default: 0}) readonly id!: number
...
}
等同於
export default {
...
props:{
id:{
type: Number,
default: 0
}
}
...
}
Watch
import { Component, Vue, Watch } from "vue-property-decorator";
export default class MyComponent extends Vue {
...
@Watch('dialogFormVisible')
dialogFormVisibleChange(newVal:boolean){
// 一些操作
}
...
}
等同於
export default {
...
watch:{
dialogFormVisible(){
// 一些操作
}
}
...
}
Provide/Inject
// App.vue
import {Component, Vue, Provide} from 'vue-property-decorator'
@Component
export default class App extends Vue {
@Provide() app = this
}
// MyComponent.vue
import {Component, Vue, Inject} from 'vue-property-decorator'
@Component
export default class MyComponent extends Vue {
@Inject() readonly app!: Vue
}
等同於
// App.vue
export default {
provide() {
return {
'app': this
}
}
}
// MyComponent.vue
export default {
inject: ['app']
}
更多裝飾器使用,參考vue-property-decorator文檔
全局聲明
*.d.ts 文件
目前主流的庫文件都是 JavaScript 編寫,TypeScript 身為 JavaScript 的超集,為支持這些庫的類型定義,提供了類型定義文件(*.d.ts),開發者編寫類型定義文件發佈到npm上,當使用者需要在 TypeScript 項目中使用該庫時,可以另外下載這個包,讓JS庫能夠在 TypeScript 項目中運行。
比如:md5
相信很多人都使用過,這個庫可以將字元串轉為一串哈希值,這種轉化不可逆,常用於敏感信息進行哈希再發送到後端進行驗證,保證數據安全性。如果我們想要在 TypeScript 項目中使用,還需要另外下載 @tyeps/md5
,在該文件夾的index.d.ts中可以看到為 md5
定義的類型。
/// <reference types="node" />
declare function md5(message: string | Buffer | Array<number>): string;
declare namespace md5 {}
export = md5;
TypeScript 是如何識別 *.d.ts
TypeScript 在項目編譯時會全局自動識別.d.ts文件,我們需要做的就是編寫.d.ts,然後 TypeScript 會將這些編寫的類型定義註入到全局提供使用。
為vue實例添加屬性/方法
當我們在使用this.$route
或一些原型上的方法時,typescript無法進行推斷,在編譯時會報屬性$route
不存在的錯誤,需要為這些全局的屬性或方法添加全局聲明
對shims-vue.d.ts做修改,當然你也可以選擇自定義*.d.ts來添加聲明
import Vue from 'vue';
import VueRouter, { Route } from 'vue-router'
declare module '*.vue' {
export default Vue;
}
declare module 'vue/types/vue' {
interface Vue {
$api: any;
$bus: any;
$router: VueRouter;
$route: Route;
}
}
自定義類型定義文件
當一些類型或介面等需要頻繁使用時,我們可以為項目編寫全局類型定義,
根路徑下創建@types文件夾,裡面存放*.d.ts文件,專門用於管理項目中的類型定義文件。
這裡我定義個global.d.ts文件:
//declare 可以創建 *.d.ts 文件中的變數,declare 只能作用域最外層
//變數
declare var num: number;
//類型
type StrOrNum = string | number
//函數
declare function handler(str: string): void;
// 類
declare class User {
}
//介面
interface OBJ {
[propName: string]: any;
[propName: number]: any;
}
interface RES extends OBJ {
resultCode: number;
data: any;
msg?: string;
}
解放雙手,transvue2ts轉換工具
改造過程最麻煩的就是語法轉換,內容都是一些固定的寫法,這些重覆且枯燥的工作可以交給機器去做。這裡我們可以藉助 transvue2ts 工具提高效率,transvue2ts 會幫我們把data、prop、watch等語法轉換為裝飾器語法。
安裝
npm i transvue2ts -g
使用
安裝完之後,transvue2ts 庫的路徑會寫到系統的 path中,直接打開命令行工具即可使用,命令的第二個參數是文件的完整路徑。
執行命令後會在同級目錄生成轉換好的新文件,例如處理view文件夾下的index.vue,轉換後會生成indexTS.vue。
處理單文件組件
transvue2ts D:\typescript-vue-admin-demo\src\pages\index.vue
=>
輸出路徑:D:\typescript-vue-admin-demo\src\pages\indexTS.vue
處理文件夾下的所有vue組件文件
transvue2ts D:\typescript-vue-admin-demo\src\pages
=>
輸出路徑:D:\typescript-vue-admin-demo\src\pagesTS
補充
不要以為有工具真就完全解放雙手,工具只是幫我們轉換部分語法。工具未能處理的語法和參數的類型定義,還是需要我們去修改的。要註意的是轉換後註釋會被過濾掉。
該工具作者在掘金對工具的介紹和實現思路
關於第三方庫使用
一些三方庫會在安裝時,包含有類型定義文件,使用時無需自己去定義,可以直接使用官方提供的類型定義。
node_modules中找到對應的包文件夾,類型文件一般都會存放在types文件夾內,其實類型定義文件就像文檔一樣,這些內容能夠清晰的看到所需參數和參數類型。
這裡列出一些在 Vue 中使用三方庫的例子:
element-ui 組件參數
使用類型定義
import { Component, Vue } from "vue-property-decorator";
import { ElLoadingComponent, LoadingServiceOptions } from 'element-ui/types/loading'
let loadingMark:ElLoadingComponent;
let loadingConfig:LoadingServiceOptions = {
lock: true,
text: "載入中",
spinner: "el-icon-loading",
background: "rgba(255, 255, 255, 0.7)"
};
@Component
export default class MyComponent extends Vue {
...
getList() {
loadingMark = this.$loading(loadingConfig);
this.$api.getList()
.then((res:RES) => {
loadingMark.close();
});
}
...
}
element-ui/types/loading,原文件里還有很多註釋,對每個屬性都做出描述
export interface LoadingServiceOptions {
target?: HTMLElement | string
body?: boolean
fullscreen?: boolean
lock?: boolean
text?: string
spinner?: string
background?: string
customClass?: string
}
export declare class ElLoadingComponent extends Vue {
close (): void
}
declare module 'vue/types/vue' {
interface Vue {
$loading (options: LoadingServiceOptions): ElLoadingComponent
}
}
vue-router 鉤子函數
使用類型定義
import { Component, Vue } from "vue-property-decorator";
import { NavigationGuard } from "vue-router";
@Component
export default class MyComponent extends Vue {
beforeRouteUpdate:NavigationGuard = function(to, from, next) {
next();
}
}
在vue-router/types/router.d.ts中,開頭就可以看到鉤子函數的類型定義。
export type NavigationGuard<V extends Vue = Vue> = (
to: Route,
from: Route,
next: (to?: RawLocation | false | ((vm: V) => any) | void) => void
) => any
還有前面所使用到的Router
、Route
,所有的方法、屬性、參數等都在這裡被描述得清清楚楚
export declare class VueRouter {
constructor (options?: RouterOptions);
app: Vue;
mode: RouterMode;
currentRoute: Route;
beforeEach (guard: NavigationGuard): Function;
beforeResolve (guard: NavigationGuard): Function;
afterEach (hook: (to: Route, from: Route) => any): Function;
push (location: RawLocation, onComplete?: Function, onAbort?: ErrorHandler): void;
replace (location: RawLocation, onComplete?: Function, onAbort?: ErrorHandler): void;
go (n: number): void;
back (): void;
forward (): void;
getMatchedComponents (to?: RawLocation | Route): Component[];
onReady (cb: Function, errorCb?: ErrorHandler): void;
onError (cb: ErrorHandler): void;
addRoutes (routes: RouteConfig[]): void;
resolve (to: RawLocation, current?: Route, append?: boolean): {
location: Location;
route: Route;
href: string;
normalizedTo: Location;
resolved: Route;
};
static install: PluginFunction<never>;
}
export interface Route {
path: string;
name?: string;
hash: string;
query: Dictionary<string | (string | null)[]>;
params: Dictionary<string>;
fullPath: string;
matched: RouteRecord[];
redirectedFrom?: string;
meta?: any;
}
自定義三方庫聲明
當使用的三方庫未帶有 *.d.ts 聲明文件時,在項目編譯時會報這樣的錯誤:
Could not find a declaration file for module 'vuedraggable'. 'D:/typescript-vue-admin-demo/node_modules/vuedraggable/dist/vuedraggable.umd.min.js' implicitly has an 'any' type.
Try `npm install @types/vuedraggable` if it exists or add a new declaration (.d.ts) file containing `declare module 'vuedraggable';`
大致意思為 vuedraggable 找不到聲明文件,可以嘗試安裝 @types/vuedraggable(如果存在),或者自定義新的聲明文件。
安裝 @types/vuedraggable
按照提示先選擇第一種方式,安裝 @types/vuedraggable
,然後發現錯誤 404 not found,說明這個包不存在。感覺這個組件還挺多人用的(周下載量18w),沒想到社區居然沒有聲明文件。
自定義聲明文件
無奈只能選擇第二種方式,說實話自己也摸索了有點時間(主要對這方面沒做多瞭解,不太熟悉)
首先在 node_modules/@types 下創建 vuedraggable 文件夾,如果沒有 @types 文件夾可自行創建。vuedraggable 文件夾下創建 index.d.ts。編寫以下內容:
import Vue from 'vue'
declare class Vuedraggable extends Vue{}
export = Vuedraggable
重新編譯後沒有報錯,解決問題。
建議及註意事項
改造過程
- 在接入 TypeScript 時,不必一次性將所有文件都改為ts語法,原有的語法也是可以正常運行的,最好就是單個修改
- 初次改造時出現一大串的錯誤是正常的,基本上都是類型錯誤,按照錯誤提示去翻譯進行修改對應錯誤
- 在導入ts文件時,不需要加
.ts
尾碼 - 為項目定義全局變數後無法正常使用,重新跑一遍伺服器(我就碰到過...)
遇到問題
- 面向搜索引擎,前提是知道問題出在哪裡
- 多看仔細文檔,大多數一些錯誤都是比較基礎的,文檔可以解決問題
- Github 找 TypeScript 相關項目,看看別人是如何寫的
寫在最後
抽著空閑時間入門一波 TypeScript,嘗試把一個後臺管理系統接入 TypeScript,畢竟只有實戰才能知道有哪些不足,以上記錄都是在 Vue 中如何使用 TypeScript,以及遇到的問題。目前工作中還未正式使用到 TypeScript,學習新技術需要成本和時間,大多數是一些中大型的公司在推崇。總而言之,多學點總是好事,學習都要多看多練,知道得越多思維就會更開闊,解決問題的思路也就越多。