項目使用的是Vue全家桶系列(vue, vuex, vue-router)構建的, 項目代碼量和業務複雜度仍是有一些. 剛開始人少時, 代碼寫起來仍是沒有問題的, 慢慢的, 隨着人員的增多, 會發現你們的代碼"風格"各異:html
凡此種種, 想起以前看過的一段話"欠的債, 早晚要還的". 有沒有辦法能夠約束下這些"風格"各異的代碼, 而且對當前工程代碼影響不是很大的? => TypeScript, 也許能夠試試.前端
TypeScript 具備類型系統,且是 JavaScript 的超集,TypeScript 在 2018年 勢頭迅猛,可謂遍地開花。vue
Vue3.0 將使用 TS 重寫,重寫後的 Vue3.0 將更好的支持 TS。 2019 年 TypeScript 將會更加普及,可以熟練掌握 TS,並使用 TS 開發過項目,將更加成爲前端開發者的優點。node
所以, 這個技能必需要學會, 因此也就邊學邊實踐, 並逐步引用到項目中實戰. 預計在12月的版本中, 將其中一個小的vue項目中所有改用TypeScript.git
由於公司是內網環境, 不可訪問外網. So, 只能回來再將代碼複寫一回了. 估計更新會比較慢.github
練手項目地址: vue-typescript-skillsvue-router
使用@vue/cli 3.0建立typescript工程.vuex
D:\vueProjects>vue create vue-typescript Vue CLI v3.9.2 ┌───────────────────────────┐ │ Update available: 4.0.5 │ └───────────────────────────┘ ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, TS, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E ? Use class-style component syntax? Yes ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes ? Use history mode for router? (Requires proper server setup for index fallback in production) No ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less ? Pick a linter / formatter config: Standard ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save ? Pick a unit testing solution: Jest ? Pick a E2E testing solution: Cypress ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? Yes ? Save preset as: vue-typescript 複製代碼
安裝成功後, 便可運行本地開發環境了.vue-cli
安裝成功後, 會生成以下目錄 :typescript
這裏咱們重點關注下4個文件:
eslint示例和解釋可參考: .eslintrc 文件示例和解釋
在對支持typescript, 可能會新增或修改2個配置,
'extends': [ 'plugin:vue/essential', '@vue/standard', '@vue/typescript' ] 複製代碼
overrides: [ { files: [ '**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)' ], env: { jest: true } } ] 複製代碼
這主要用於複寫eslint配置配置, 此處是針對__tests__, 以及tests/units目錄下的js/ts/jsx/ts文件, 修改配置規則, 此處是修改env
爲jest:true
ts的語言服務須要.d.ts
文件來識別類型,這樣才能作到相應的語法檢查和智能提示. 咱們本身編寫的.d.ts
文件直接放在項目的目錄下,ts本身會去識別,不用咱們作什麼操做,更加詳細的資料能夠看一下TypeScript-聲明文件
.vue
文件, 並將全部導入的.vue
文件都按VueConstructor<Vue>
處理若是一個目錄下存在一個tsconfig.json文件,那麼它意味着這個目錄是TypeScript項目的根目錄。 tsconfig.json文件中指定了用來編譯這個項目的根文件和編譯選項。 一個項目能夠經過如下方式之一來編譯:
tsconfig.json中詳細配置項及說明, 請移步至: TypeScript-項目配置-tsconfig.json
此規則集是Vue-TypeScript項目的基本配置。除了設置解析器和插件選項外,它還會關閉規則集中的一些衝突規則eslint:recommended。所以,當與其餘可共享配置一塊兒使用時,此配置應放在extends數組的末尾。 例如:
// .eslintrc.js: module.exports = { extends: [ 'plugin:vue/essential', 'eslint:recommended', '@vue/typescript' ] } 複製代碼
在此以前, 咱們可能須要先了解下ES7裝飾器(Decorator)在Javascript中的使用
所以, 會有如下寫法上的改變
@Component(options) options 中須要配置 decorator 庫不支持的屬性, 如: components, filters, directives等
示例:
<template> <div> <input-demo :demo="demo"></input-demo> </div> </div> </template> <script lang="ts"> import Component from 'vue-class-component' import { Emit, Inject, Model, Prop, Provide, Ref, Vue, Watch, PropSync } from 'vue-property-decorator' import InputDemo from './InputDemo.vue' @Component({ components: { InputDemo } }) export default class Demo extends Vue { // data count = 0 demo = '123' mounted () { window.console.log('bar=> ', this.bar) window.console.log('foo=> ', this.foo) window.console.log('optional=> ', this.optional) } } </script> 複製代碼
在使用Vue進行開發時咱們可能須要用到混合,在TypeScript中, 咱們能夠這麼寫
在如下示例中mixins/index.ts中, 咱們在data中添加了一個屬性mixinVal, 值爲: 'Hello Mixin'
// 定義要混合的類 mixins/index.ts import Vue from 'vue' import Component from 'vue-class-component' @Component // 必定要用Component修飾 export default class myMixins extends Vue { mixinVal: string = 'Hello Mixin' } 複製代碼
而後, 在其餘組件中使用它
<template> <div> <hello-world msg='hello world'></hello-world> </div> </template> <script lang="ts"> import Vue from 'vue' import Component, { mixins } from 'vue-class-component' import { Emit, Inject, Model, Prop, Provide, Watch, PropSync, Ref } from 'vue-property-decorator' import HelloWorld from '../components/HelloWorld.vue' import mixinDemo from './mixin' @Component({ components: { HelloWorld // 組件注入 } }) export default class App extends mixins(mixinDemo) { // data message = 'hello' mounted () { // 此時, 就可使用this.mixinVal window.console.log('mixinVal => ', this.mixinVal) // 輸出: 'Hello Mixin' } } </script> 複製代碼
export default class App extends Vue { // data message = 'hello' name = 'dmax' child: number | string = 'james' } 複製代碼
等價於:
export default { name: 'App', data () { return { message: 'hello', name: 'dmax', child: 'james' } } } 複製代碼
// 計算屬性 get msg () { return 'computed ' + this.message } 複製代碼
等價於:
computed: { msg(){ return 'computed ' + this.message } } 複製代碼
@Watch(path: string, options: WatchOptions = {})
@Watch 裝飾器接收兩個參數:
@Watch('child') onChildChanged (val: string, oldVal: string) { if (val !== oldVal) { window.console.log(val) } } 複製代碼
等價於:
watch: { 'child': { handler: 'onChildChanged', immediate: false, deep: false } }, method: { onChildChanged(val, oldVal) { if (val !== oldVal) { window.console.log(val) } } } 複製代碼
也能夠寫成: @Watch('child', { immediate: true, deep: true })
, 等價於:
watch: { 'child': { handler: 'onChildChanged', immediate: true, deep: true } }, method: { onChildChanged(val, oldVal) { if (val !== oldVal) { window.console.log(val) } } } 複製代碼
@Model Vue組件提供model: {prop?: string, event?: string} 讓咱們能夠定製prop和event. 默認狀況下, 一個組件上的v-model會:
value
用做 prop
input
用做 event
,可是一些輸入類型好比單選框和複選框按鈕可能想使用 value prop來達到不一樣的目的。使用model選項能夠迴避這些狀況產生的衝突。下面是Vue官網的例子
Vue.component('my-checkbox', { model: { prop: 'checked', event: 'change' }, props: { // this allows using the `value` prop for a different purpose value: String, // use `checked` as the prop which take the place of `value` checked: { type: Number, default: 0 } }, // ... }) <my-checkbox v-model="foo" value="some value"></my-checkbox> 複製代碼
上述代碼至關於:
<my-checkbox :checked="foo" @change="val => { foo = val }" value="some value"> </my-checkbox> 複製代碼
即foo雙向綁定的是組件的checke, 觸發雙向綁定數值的事件是change
使用vue-property-decorator提供的@Model改造上面的例子.
Parent.vue
<template> <div> <child v-model="price"></child> <div> v-model(price) => {{price}} </div> </div> </template> <script lang="ts"> import { Vue, Component, Prop } from 'vue-property-decorator' import Child from './Child.vue' @Component({ components: { Child } }) export default class Parent extends Vue { price = 'hello price' } </script> 複製代碼
Child.vue
<template> <div> <input type="text" :value="value" @input="changed"/> </div> </template> <script lang="ts"> import { Vue, Component, Prop, Model, Emit } from 'vue-property-decorator' @Component export default class Child extends Vue { @Model('input') value!: boolean @Emit('input') changed (ev:any) { return ev.target.value } } </script> 複製代碼
最終效果可能爲:
也能夠經過clone git庫 vue-typescript-skills, 運行本地服務後進入http://localhost:8080/#/model, 看到效果.
@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
@Prop裝飾器接收一個參數,這個參數能夠有三種寫法:
示例:
@Component export default class Hello extends Vue { // child, 必傳, child! => 表示不須要構建器進行初始化 @Prop({ type: [String, Number], required: true }) readonly child!: string | number // propA, 非必傳, 類型能夠是number | undefined @Prop(Number) readonly propA: number | undefined // propB, 非必傳, 類型能夠是number | undefined, propB! => 表示不須要構建器進行初始化 @Prop({ default: 'default value' }) readonly propB!: string // propC, 非必傳, 構建器能夠是String|Boolean, 值類型能夠爲: string | boolean | undefined @Prop([String, Boolean]) readonly propC: string | boolean | undefined } 複製代碼
等價於:
export default { name: 'Hello', props: { child: { required: true, type: [String, Number] }, propA: { type: Number }, propB: { required: false, type: String, default: 'default value' }, propC: { type: [String, Boolean] } } } 複製代碼
@PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {})
@PropSync裝飾器與@prop用法相似,兩者的區別在於: @PropSync 裝飾器接收兩個參數:
@PropSync 會生成一個新的計算屬性。 示例:
import { Vue, Component, PropSync } from 'vue-property-decorator' @Component export default class MyComponent extends Vue { @PropSync('name', { type: String }) syncedName!: string } 複製代碼
等價於
props: { name: { type: String } }, computed: { syncedName: { get() { return this.name }, set(value) { this.$emit('update:name', value) } } } 複製代碼
@PropSync須要配合父組件的.sync
修飾符使用
@Emit(event?: string)
import { Vue, Component, Emit } from 'vue-property-decorator' @Component export default class MyComponent extends Vue { count = 0 @Emit('reset') public resetCount() { this.count = 0 } @Emit() public addToCount (n: number) { this.count += n } @Emit() public returnValue () { return 10 } @Emit() public onInputChange (e:any) { return e.target.value } @Emit() public promise () { return new Promise(resolve => { setTimeout(() => { resolve(20) }, 0) }) } } 複製代碼
等價於
export default { data() { return { count: 0 } }, methods: { addToCount(n) { this.count += n this.$emit('add-to-count', n) }, resetCount() { this.count = 0 this.$emit('reset') }, returnValue() { this.$emit('return-value', 10) }, onInputChange(e) { this.$emit('on-input-change', e.target.value, e) }, promise() { const promise = new Promise(resolve => { setTimeout(() => { resolve(20) }, 0) }) promise.then(value => { this.$emit('promise', value) }) } } } 複製代碼
@Provide接收一個參數:
若是爲了不命名衝突, 可使用 ES6 的 Symbol 特性做爲 key
@Inject 裝飾器一個參數, 該參數有兩種要能:
示例:
import { Component, Inject, Provide, Vue } from 'vue-property-decorator' const symbol = Symbol('baz') @Component export class MyComponent extends Vue { @Inject() readonly foo!: string @Inject('bar') readonly bar!: string @Inject({ from: 'optional', default: 'default' }) readonly optional!: string @Inject(symbol) readonly baz!: string @Provide() foo = 'foo' @Provide('bar') baz = 'bar' } 複製代碼
等價於:
const symbol = Symbol('baz') export const MyComponent = Vue.extend({ inject: { foo: 'foo', bar: 'bar', optional: { from: 'optional', default: 'default' }, [symbol]: symbol }, data() { return { foo: 'foo', baz: 'bar' } }, provide() { return { foo: this.foo, bar: this.baz } } }) 複製代碼
顧名思義就是響應式的注入, 會同步更新到子組件中. 好比下例能夠實如今 input 中的輸入實時注入到子組件中. 示例: Parent.vue
<template> <div> <input type="text" v-model="bar"> <Child /> </div> </template> <script lang="ts"> import { Vue, Component, Prop, ProvideReactive } from 'vue-property-decorator' import Child from './Child.vue' @Component({ components: { Child } }) export default class Parent extends Vue { @ProvideReactive() private bar = 'deeper lorry' } </script> 複製代碼
Child.vue
<template> <div > InjectReactive: {{bar}} </div> </template> <script lang="ts"> import { Vue, Component, Prop, InjectReactive } from 'vue-property-decorator' @Component export default class Child extends Vue { @InjectReactive() private bar!: string } </script> 複製代碼
最終效果可能以下:
也能夠經過clone git庫 vue-typescript-skills, 運行本地服務後進入http://localhost:8080/#/provide, 看到效果.
@Ref(refKey?: string)
@Ref裝飾器接收一個可選參數:
<template> <div> <span>Name:</span> <input type="text" v-model="value" ref='name' /> </div> </template> <script lang="ts"> @Component export default class RefComponent extends Vue { @Ref('name') readonly name!: string; value = 'lorry' mounted() { window.console.log(this.inputName); // <input type="text"> } } </script> 複製代碼
等價於:
<template> <div> <span>Name:</span> <input type="text" v-model="value" ref='name' /> </div> </template> <script lang="ts"> @Component export default { data(){ return { value: 'lorry' } }, computed: { inputName(){ return this.$refs.name } }, mounted() { window.console.log(this.inputName); // <input type="text"> } } </script> 複製代碼
directives 具體的介紹能夠看 Vue 的官方介紹.
示例:
<template> <span v-demo:foo.a="1+1">test</span> </template> <script lang="ts"> @Component({ directives: { demo: { bind(el:any, binding:any, vnode:any) { var s = JSON.stringify el.innerHTML = 'name: ' + s(binding.name) + '<br>' + 'value: ' + s(binding.value) + '<br>' + 'expression: ' + s(binding.expression) + '<br>' + 'argument: ' + s(binding.arg) + '<br>' + 'modifiers: ' + s(binding.modifiers) + '<br>' + 'vnode keys: ' + Object.keys(vnode).join(', ') }, } }, }) export default class App extends Vue {} </script> 複製代碼
在練習時, 發如今定義組件時漏寫@Component裝飾器時, 會致使運行data/prop/model屬性報錯:
Property or method "hello" is not defined on the instance but referenced during render 複製代碼
錯誤源代碼:
<template> <div> {{hello}} </div> </template> <script lang="ts"> import { createNamespacedHelpers } from 'vuex' import Component from 'vue-class-component' import { Emit, Inject, Model, Prop, Provide, Ref, Vue, Watch, PropSync } from 'vue-property-decorator' const { mapState, mapActions } = createNamespacedHelpers('myMod') // @Component ===> 注意此處的@Component裝飾器被註釋了, 起用此行, 便可解決異常 export default class UseVuex extends Vue { hello:string = this.$store.state.myMod.someField } </script> 複製代碼
Component
裝飾器如工程當中有使用到window.SystemJS, 若是在ts中不聲明直接使用, 會提示錯誤. 諸如此類, 就須要對全局變量/方法進行合適的類型聲明.
在 src 下的 shims-tsx.d.ts
中加入須要聲明的代碼, 以下所示:
declare global { interface Window { SystemJS: any; // 若是不肯定類型, 可定義爲any } } 複製代碼
import .vue
的文件的時候,要補全 .vue
的後綴,不然會提示語法錯誤或找不到模塊
經過這幾天的嘗試和試驗, 整體來講, 有一點吸引力的, 畢竟vue的寫法也很隨意, 多加入一些強制性的校驗, 項目代碼的健壯性應該會加強很多. 後續會慢慢在項目中推行, 也會慢慢進入踩坑中, 後續再持續更新, 敬請關注!