前言:
關於Vue2.x 的TS改造,其實沒有啥好說的。
對於vue-cli項目來說,從新跑一遍 vue create xxx-project ,選擇Manually select features ,重新選擇上typescript 選項即可。或者直接vue add typescript也可。
網上太多的資料,這里也推薦一些我覺得還可的(我是自己搞的,個人感覺不難吧,哈哈)
-
https://www.tslang.cn/docs/handbook/migrating-from-javascript.html
-
https://blog.logrocket.com/vue-typescript-tutorial-examples/
-
https://github.com/ffxsam/vue-typescript-cookbook#initial-set-up
對於webpack,就是增加一下ts-loader,然后增加tsconfig.json,配置ts屬性,再在eslint增加 ts代碼規范。然后就去把老的項目文件改為ts文件,就好了。就這么一句話而已^_^,畢竟如今都2021了(畢竟TS已經流行多年了),教程實在太多了。
項目配置注意事項:
本篇講的是需要的一些注意事項,以及一些實現方式的異同,以及本人的觀點(不妥之前請留言,多謝)
WebPack配置修改
-
增加TS后綴:extensions: ['.js', '.vue', '.json', '.ts'],
-
增加ts-loader: {test: /\.ts$/, loader: 'ts-loader', exclude: /node_modules/, options: {appendTsSuffixTo: [/\.vue$/],}}
TypeScript配置文件
項目根目錄創建tsconfig.json文件,需要注意下面幾個配置:
-
"strictPropertyInitialization": false, // strict默認為true——必須要確保每個實例的屬性都會初始值
-
"noImplicitAny": false, // false表示運行隱式的any類型,也就是允許不設置任何類型, 這個設置運行js文件直接改成ts文件
-
"allowJs": true, // 初期改造,肯定是JS與TS並行跑
-
"strictFunctionTypes": false, // 啟用 vuex-class 需要開啟此選項
-
"target": "es5", // 編譯輸出目標 ES 版本
-
"skipLibCheck": true,
-
"jsx": "preserve", // 在.tsx文件里支持JSX
-
"experimentalDecorators": true, // 啟用裝飾器
-
"strictNullChecks": true, // 當你聲明一個變量時,它不會自動地包含null或undefined。
其他的,按照官方的來就可。
想noImplicitAny 就是比較雞賊的玩法,但是你一個老項目的改造,可以邊改變調整。不然,改着改着,就會失去重構信心。
eslint解析規則增加TS配置項
根目錄下,.eslintrc.js,參考配置
extends: [ 'plugin:vue/recommended', 'eslint:recommended', '@vue/typescript/recommended', '@vue/prettier', '@vue/prettier/@typescript-eslint' ], parserOptions: { ecmaVersion: 2020, },
其實這個配置,看大家隨意。默認vue-cli 生成的文件就好,沒有vue-cli生成一個demo項目,copy一份。我們當然得遵從鵝廠的內部代碼規范,就不貼了。
TypeScript的聲明文件
官方文檔:https://www.tslang.cn/docs/handbook/declaration-files/introduction.html
import Vue, { VNode } from 'vue'; declare global { interface Window { // 全局變量 i18n: any; eCharts: any; } } declare module 'vue/types/vue' { interface Vue { $bus: Vue; $route: any; $router: any; CancelToken: any; $message: any; $confirm: any; } } declare global { namespace JSX { interface Element extends VNode {} interface ElementClass extends Vue {} interface IntrinsicElements { [elem: string]: any; } } }
項目改造到這里就基本結束了
TS一些注意事項
這部分對於剛剛改造,需要提醒成員的事項
TS類型
any
any,這個東西好用,但是,如果完全放開的話,相信我,帶最最后可能基本都是any
但是項目改造初期,可以先用any 頂替,后面有有時間,在進一步細化。這個度量,其實不是很好衡量。對於新手,代碼合並的時候,還是打回any。
可選屬性vs null undefined
null 和 undefined 是 ts 中的基礎類型,分別具有值 null 和 undefined,默認情況下它們是所有類型的子類型,即可以賦值給任意類型。
null與undefined是所有其它類型的一個有效值。 這也意味着,你阻止不了將它們賦值給其它類型,就算是你想要阻止這種情況也不行。
null的發明者,Tony Hoare,稱它為價值億萬美金的錯誤。
tsconfig.js 文件中設置 strictNullChecks 為 true 時,就不能將 null 和 undefined 賦值給除它們自身和 void 之外的任意類型了。
在這種嚴格檢查的情況下,如果你確實在某個地方想要給一個其他類型的值設置初始值為空,然后再賦值,可以使用聯合類型來實現。
let test: string | null = 'hi'
null 和 undefined 是區別的
string|undefined、string|null 和 string|undefined|null 是三種不同的類型。
如果設置了 "strictNullChecks": true,可選參數會被自動加上 |undefined
let test?: string = 'hi'
interface/class/abstract class/type
Declaration Type | Namespace | Type | Value |
---|---|---|---|
Namespace | X | X | |
Class | X | X | |
Enum | X | X | |
Interface | X | ||
Type Alias | X | ||
Function | X | ||
Variable | X |
不算symbol,js中有6種基本類型,number,string,boolean,null, undefined, object。但是只依靠這幾種類型,來描述某個函數需要傳什么樣的參數,是遠遠不夠的,這也是interface的使命--描述一個值(value)的形狀(type)。
class首先也具有interface的能力,描述一個形狀,或者說代表一種類型。此外class還提供了實現,也就是說可以被實例化;
interface可以extends class,此時的class承擔類型的角色。這里對於之前寫java的我來說,有點WTF。其實對於undefined 有自己的類型叫做 undefined,java程序員也表示懵逼。
TypeScript 通過采用結構化類型系統來體現 JavaScript 的動態特性,並且在類型推斷方面做得非常出色,這意味着你不必像 C#或 Java 那樣明確表達類型。
TypeScript 的設計目標之一不是為了創建一個“正確的類型系統”,而是“在正確性和生產力之間取得平衡”。——TypeScript 編譯器不會強制你聲明類型,類型安全的程度由你自己來決定。你甚至可以決定在項目的不同區域應用不同級別的類型安全嚴格程度。這種靈活性不是傳統的靜態類型語言可以提供的。
這里不像講太多,覺得typescript手冊就非常詳細:https://www.tslang.cn/docs/handbook/basic-types.html
Vue升級方案對比
vue2升級到TS改造方案有很多種。
傳統方案:vue-property-decorator
vue2對ts的支持主要是通過vue class component。這里主要依賴裝飾器。順手安利下《從java注解漫談到typescript裝飾器——注解與裝飾器》。
此外,可以拓展了解一下元編程。
vue2比較令人詬病的地方還是對ts的支持,對ts支持不好是vue2不適合大型項目的一個重要原因。其根本原因是Vue依賴單個this上下文來公開屬性,並且vue中的this比在普通的javascript更具魔力(如methods對象下的單個method中的this並不指向methods,而是指向vue實例)。換句話說,尤大大在設計Option API時並沒有考慮對ts引用的支持)
具體用法算是比較詳細吧:https://github.com/kaorun343/vue-property-decorator
<template> <div> <sidebar/> </div> </template> <script lang="ts"> import { Component, Vue, Watch } from 'vue-property-decorator'; import { Action, Mutation, State } from 'vuex-class'; import sidebar from 'layout/sidebar'; @Component({ components: { sidebar, }, }) export default class App extends Vue { private loading = true @State(state => state.user.theme) private readonly theme @Mutation('user/setThemeModel') private setThemeModel @Action('user/getUserInfo') private getUserInfo @Watch('$route.params.id') private handler() { // TODO } private get testCount() { // TODO } private mounted(): void { } } </script>
我個人不太喜歡這個風格,但是,但是周圍都是這么樣用,現在重構的項目就采用這個風格了
typescript mixin
我對mixin不太感冒。既然要用,一定要注意以下幾點:
mixins合並規律:
-
覆蓋的:data,props,methods,computed,inject
-
直接替換:el,template,propData
-
合並的:
-
methods,權重高的函數先執行
-
生命周期函數,watch監聽回調函數,權重小的 先執行
mixins混合權重
類似css權重規則(其實沒有權重這個東西,但是結果是一樣的,只是覺得這樣好理解而已)
-
*、全局 選項
-
1、......省略無數可能存在的嵌套 mixin
-
10、組件 - mixin - mixin
-
100、組件 - mixin
-
1000、組件選項
更多參看《vue mixins、Vue.extend() 、extends使用注意事項筆記》
用起來很簡單
import { Component, Mixins, Vue } from 'vue-property-decorator'; // mixin @Component export default class PageLeave extends Vue { // TODO } // 組件 @Component({ components: { TitleBar, }, }) export default class CardPanel extends Mixins(PageLeave,OtherMixin) { //TODO }
不過TS的vue項目,mixin基本被我排除在外。
早在2016年中期,丹·阿布拉莫夫(Dan Abramov)就寫了《mixin被認為是有害的》(mixin Considered Harmful),他在書中辯稱,將 mixin 用於在 React 組件中重用邏輯是一種反模式,主張遠離它們。
他提到的關於 React mixins 的缺點同樣適用於 Vue。
OPP本來就可以解決一切呀,不香么!
vue-property-decorator方案缺點
-
vue class component與js的vue組件差異太大,另外需要引入額外的庫,學習成本大幅度增高。
-
依賴於裝飾器語法。而目前裝飾器目前還處於stage2階段(可查看tc39 decorators),在實現細節上還存在許多不確定性,這使其成為一個相當危險的基礎。
-
復雜性增高。采用Vue Class Component且需要使用額外的庫,相比於簡單的js vue組件,顯然復雜化。
個人更偏下一下方案。
tsx組合方案:Vue Components + TypeScript
我起初是寫react的,后寫vue,所以更喜這種風格
import Vue, { VueConstructor, CreateElement, VNode } from 'vue'; interface InputInstance extends Vue { composing: boolean; } export default (Vue as VueConstructor<InputInstance>).extend({ name: 'demo-input', props: {}, data() {}, // TODO 更Vue其它組件一樣 render(h: CreateElement): VNode { // TODO 邏輯 const classes = [] const wrapperAttrs = {...this.$attrs}; const wrapperEvents = {...this.$listeners}; // JSX 語法 return ( <div class={classes} {...{ attrs: wrapperAttrs, on: wrapperEvents }}> <input/> </div> ); } }
這里的mixin還是和之前的一樣
// Count.mixin.ts import Vue from 'vue' import { mapGetters } from 'vuex' export default Vue.mixin({ computed: { ...mapGetters(['count']) }, methods: {} }) // Count.vue export default Vue.extend<{}, Methods, Computed, {}>({ mixins: [CountMixin], methods: {} })
多個mixin混合
import CountMixin, { Computed as CountComputed } from './Count.mixin' import LoadingMixin, { Computed as LoadingComputed } from './Loading.mixin' type Computed = CountComputed & LoadingComputed interface Methods { incrementCount: Function decrementCount: Function } export default Vue.extend<{}, Methods, Computed, {}>({ mixins: [CountMixin, LoadingMixin], methods: {} })
但是,上面mixin的data 類型 糊了……
推薦實現
interface CountBindings extends Vue { count: number } export default (Vue as VueConstructor<CountBindings>).extend({ mixins: [CountMixin], methods: {} })
具體可以參看,https://medium.com/glovo-engineering/vue-components-typescript-ff62db05829c
composition-api
這個首先需要npm i -S @vue/composition-api
然后全局注入
import VueCompositionApi from "@vue/composition-api"; Vue.use(VueCompositionApi);
其實,這個我也琢磨中。晚點在補充這方面的內容。
直接升級Vue3.0
我是沒有怎么做,如果是重寫性重構,我肯定會直接用Vue3.0。但是對於龐大的項目,重構直接用3.0,還是怕怕。
雖然尤大大說vue2 與vue3,不會像angular2 與其后代版本差異那么大,但是,我還是緩緩先
Vuex Store的痛
在ts里面使用vuex非常的蛋疼。
vuex ts版相關的vuex-class和vuex-module-decorators兩個庫應該是目前用的最多的(個人認為)。
https://www.npmtrends.com/vuex-aggregate-vs-vuex-class-vs-vuex-module-decorators
stars | issues | updated | created | |
---|---|---|---|---|
vuex-class | 1,653 | 18 | Oct 12, 2020 | Jan 14, 2017 |
vuex-module-decorators | 1,595 | 123 | May 8, 2021 | May 1, 2018 |
如果是老舊項目,個人推薦先使用vuex-class過度。
暫時先整理到這里,周末早點睡。后續再跟進……
轉載本站文章《vue2.x老項目typescript改造過程經驗總結》,
請注明出處:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue/8637.html