項目使用的是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-聲明文件
因爲 TypeScript 默認並不支持 *.vue 後綴的文件,因此在 vue 項目中引入的時候須要建立一個 vue-shim.d.ts 文件,放在項目項目對應使用目錄下,例如 src/vue-shim.d.ts
若是一個目錄下存在一個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>
複製代碼
經過這幾天的嘗試和試驗, 整體來講, 有一點吸引力的, 畢竟vue的寫法也很隨意, 多加入一些強制性的校驗, 項目代碼的健壯性應該會加強很多. 後續會慢慢在項目中推行, 也會慢慢進入踩坑中, 後續再持續更新, 敬請關注!