TypeScript+vue使用与迁移经验总结
源寶導讀:ERP平臺的前端底層使用了Vue作為組件的基礎架構,而使用了TypeScript語言進行組件的封裝與開發。本文將簡要介紹平臺在使用TypeScript和Vue框架進行老功能重構時的經驗總結。
一、背景
下面主要探討是以下三個方面:
目前項目中使用到的vue+ts的哪些特性,還有哪些特性值得去使用,不會涉及到太多的ts語法知識;
老項目的遷移為ts,有哪些點需要改造;
各抒己見,探討下各位都有哪些心得和見解。
二、為什么要用typescript
TypeScript簡單介紹:
是 JavaScript 的強類型版本。然后在編譯期去掉類型和特有語法,生成純粹的 JavaScript 代碼。由于最終在瀏覽器中運行的仍然是 JavaScript,所以 TypeScript 并不依賴于瀏覽器的支持,也并不會帶來兼容性問題。
TypeScript 是 JavaScript 的超集,這意味著他支持所有的 JavaScript 語法。并在此之上對 JavaScript 添加了一些擴展,如 class / interface / module 等。這樣會大大提升代碼的可閱讀性。
總結優勢:
靜態類型檢查: 類型校驗,能夠避免許多低級代碼錯誤;
IDE 智能提示: 使用一個方法時,能清楚知道方法定義的參數和類型和返回值類型;使用一個對象時,只需要.就可以知道有哪些屬性以及屬性的類型;
代碼重構: 經過不停的需求迭代,代碼重構避免不了,在重構時,如果前期有清晰和規范的接口定義、類定義等,對于重構幫助很大;
規范性和可讀性: 類似于強類型語言,有了合理的類型定義、接口定義等,對于代碼實現的規范性和可讀性都有很大提高,不然搜索整個項目這個方法在哪里調用、怎么定義等。
個人認為最有價值點:寫代碼前,會先構思功能需求的整體代碼架構。
三、安裝和起步
一般我們會面臨兩個情況:
新項目創建;
覺得ts不錯,想將老項目切換為vue+ts。
3.1、新項目起步
安裝vue-cli3.0;
vue create vue-ts-hello-world;
選擇Manually select features,勾選typescript。其他配置根據項目情況勾選。
3.2、老項目切換為vue+ts
安裝ts依賴(或使用yarn);
yarn add vue-class-component vue-property-decorator;
yarn add ts-loader typescript tslint tslint-loader tslint-config-standard —dev。
配置 webpack,添加ts-loader和tslint-loader;
添加 tsconfig.json;
備注: ts 也可支持 es6 / es7,在 tsconfig.json中,添加對es6 / es7的支持。
"lib": ["dom","es5","es6","es7","es2015.promise" ]添加 tslint.json 或者 prettierrc(可以視情況而定)。
讓 ts 識別 .vue。
而在代碼中導入 .vue 文件的時候,需要寫上 .vue 后綴。原因還是因為 TypeScript 默認只識別 .ts 文件,不識別 *.vue 文件。
添加vue-shim.d.ts,讓vue文件給vue模塊來編譯。
改造 .vue文件,將vue中script切換為<script lang="ts">;
改造.js文件,修改為ts語法,定義類型等。
四、vue+ts常用的裝飾器
? ? 這里主要用到了vue-property-decorator,這個是在vue-class-component基礎上做了一層增強,新增了一些裝飾器,使用更加便捷。這里只分享一些常用的,對于老項目改寫vue文件很有用:
4.1、@Component
? ? 標識該vue文件是一個組件,并且可以引入其他組件。
非ts版本:
import MyComponent from '@/components/MyComponent' export default {components: {MyComponent} }ts版本:
import { Vue, Component } from 'vue-property-decorator' import MyComponent from '@/components/MyComponent' @Component({components: {MyComponent} }) export default class YourComponent extends Vue { }備注:這里不管有沒有引入其他組件,都必須要使用@Component,目的是為了注冊這個組件。否則在其他組件各種莫名其妙的問題。比如:路由找不到組件,而且不會報錯。
4.2、@Prop
非ts版本:
export default {props: {propA: {type: Number},propB: {default: 'default value'},propC: {type: [String, Boolean]},propD: {type: Object,default: () => {},validator(val: object) {return val.prop = '1'}}} }ts版本:
import { Vue, Component, Prop } from 'vue-property-decorator'@Component export default class YourComponent extends Vue {@Prop(Number)readonly propA: number | undefined@Prop({ default: 'default value' })readonly propB!: string@Prop([String, Boolean])readonly propC: string | boolean | undefined// 也可以一起@Prop({type: Object, default: () => {},validator(val: object) {return val.prop = '1'}})readonly propD!: object // 只是舉例,一般會定義一個interface }4.3、@Watch
非ts版本:
export default {watch: {child: {handler: 'onChildChanged',immediate: false,deep: false},person: [{handler: 'onPersonChanged1',immediate: true,deep: true},{handler: 'onPersonChanged2',immediate: false,deep: false}]},methods: {onChildChanged(val, oldVal) {},onPersonChanged1(val, oldVal) {},onPersonChanged2(val, oldVal) {}} }ts版本:
import { Vue, Component, Watch } from 'vue-property-decorator'@Component export default class YourComponent extends Vue {@Watch('child')onChildChanged(val: string, oldVal: string) {}@Watch('person', { immediate: true, deep: true })onPersonChanged1(val: Person, oldVal: Person) {}@Watch('person')onPersonChanged2(val: Person, oldVal: Person) {} }4.4、@Provide和@Inject
? ? 場景:一般用于父級嵌套比較深的子孫vue組件,但是數據不是很方便傳到深層級vue組件中,利用樹型結構組件。
非ts版本:
// 父組件 provide () {return {OptionGroup: this} }// 子孫組件 inject: ['OptionGroup']ts版本:
父組件:
@Provide()getObj () {return this}子孫組件:
@Inject() getObj!: anyget obj() {return this.getObj() }Privide的弊端:
依賴注入它將你應用程序中的組件與它們當前的組織方式耦合起來,使重構變得更加困難;
同時所提供的屬性是非響應式的。這是出于設計的考慮,因為使用它們來創建一個中心化規模化的數據跟使用 $root做這件事都是不夠好的。
建議:
一般不推薦過度使用。
provide 和 inject的綁定并不是可響應的,這是刻意為之的。但是,如果你傳入了一個可監聽的對象,那么其對象的屬性還是可響應的;
如果你想要共享的這個屬性是你的應用特有的,而不是通用化的,或者如果你想在祖先組件中更新所提供的數據,那么這意味著你可能需要換用一個像Vuex這樣真正的狀態管理方案了。
4.5、@Ref
非ts版本:
export default {computed() {anotherComponent () {return this.$refs.anotherComponent},button () {return this.$refs.aButton}} }ts版本:
import { Vue, Component, Ref } from 'vue-property-decorator' import AnotherComponent from '@/Components/another-component.vue'@Component export default class YourComponent extends Vue {@Ref() readonly anotherComponent!: AnotherComponent@Ref('aButton') readonly button!: HTMLButtonElement// 我們目前是這樣使用的$refs!: {popover: anysearch: HcProjectSelectSearchtree: HcProjectTree} }4.6、@Emit
用的很少,參數和時機不是很好控制。
非ts版本:
export default {methods: {handleClick(e) {this.$emit('click', e)},loadData() {const promise = new Promise(resolve => {setTimeout(() => {resolve(20)}, 0)})promise.then(value => {this.$emit('load', value)})}} }ts版本:
import { Vue, Component, Emit } from 'vue-property-decorator'@Component export default class YourComponent extends Vue {@Emit('click')handleClick(e) {// todo}@Emit()promise() {return new Promise(resolve => {setTimeout(() => {resolve(20)}, 0)})} }五、mixin改寫
定義mixin:
export const cusMixin = {mounted() {this.$refs = {}// $0 instanceof HTMLElement// this.$refs = {}console.log('mixin mounted')},beforeUpdate() {this.$refs = {}// console.log('global mounted')},updated() {this.$refs = {}// console.log('global mounted')} }引入mixin:
import { Vue, Component } from 'vue-property-decorator' import cusMixin from '@/mixin'@Component({components: {},mixins: [cusMixin] }) export default class YourComponent extends Vue {}// 或者嘗試使用 import { Component, Mixins, Vue } from 'vue-property-decorator'; import { MyOtherMixin } from './MyOtherMixin';@Component export class MyMixin extends Vue {private created() {console.log('what?');} }@Component // 繼承多個mixin,使用數組 [MyMixin, MyOtherMixin] export default class App extends Mixins(MyMixin) { private test = "test";private laowang = 'laowang';created() {console.log(this.test)console.log(this.Kitchen)console.log(this.Tv)}}六、vue識別全局的方法和變量
vue-shim.d.ts文件中,增加如下代碼:
七、vuex的改寫
? ? 關于store的改造,配置和結構和原來一樣,具體編碼設計沒有特定套路,根據項目具體設計改寫為ts的語法。
? ? 主要是關于ts在vue如何使用,目前主流的方案是vue-class-component + vuex-class,一般常用的mapGetters和mapActions改寫:
yarn add vuex-class非ts版本:
import { mapGetters, mapActions } from 'vuex' export default Vue.extend({computed: {...mapGetters({'name','age'})},methods: {...mapActions(['setNameAction'])} })ts版本:
import { Vue, Component } from 'vue-property-decorator' import { Getter, Action } from 'vuex-class' import { Test } from '@/store'export default class YourComponent extends Vue {@Getter('name') name: string@Getter('age') age: number@Action('setNameAction') setNameAction: Functionget innerName (): string {return this.name}get innerAge (): number {return this.age}setName (name: string) {this.setNameAction(products)} }備注:tsconfig.json需要調整下:
{"compilerOptions": {// 啟用 vue-class-component 及 vuex-class 需要開啟此選項"experimentalDecorators": true,// 啟用 vuex-class 需要開啟此選項"strictFunctionTypes": false} }八、vue render jsx語法改寫
? ? 改寫的原理還是和上面類似,都是借助目前流行的兩個庫,除了使用vue-property-decorator以外,還需要借助vue-tsx-support,vue-tsx-support是在Vue外面包裝了一層,將prop、event等以泛型的方式加了一層ts接口定義傳了進去,目的是為了防止ts的類檢查報錯。
步驟:
引入 yarn add vue-tsx-support --dev;
導入ts聲明,在main,ts中import "vue-tsx-support/enable-check";
在vue.config.js中extensions添加.tsx。
使用:
? ? 這里jsx改寫為tsx大致簡單了解下,如果大家有興趣,以后可以一起學習探討下。
九、思考
關于老項目ts的改造,如何才能平滑過渡,不影響現有的功能。
在vue中ts的實踐,數據、視圖、控制器分層設計的問題。
------ END ------
作者簡介
羅同學:?研發工程師,目前負責ERP建模平臺的設計與開發工作。
也許您還想看
從案例角度解析建模平臺動態規則引擎
WEB頁面前端性能診斷方法與實踐
前端異步對象的原理與使用方法
Web頁面適配移動端方案研究
總結
以上是生活随笔為你收集整理的TypeScript+vue使用与迁移经验总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何给Blazor.Server加个AP
- 下一篇: html5倒计时秒杀怎么做,vue 设