Vue + Typescript 的開發實踐

概述

VUE 是前端三劍客之一, 以前一直處在寫過 demo 的地步, 沒有很深刻的體會, 此次公司由於招不到 react 的人, 因此但願嘗試將技術棧換爲 vue, 以便更方便招人.javascript

因而就有了 VUE 的踏坑之旅, 並且一開始就是配合 ts 來食用, 由於在 react 中 ts 的配合至關的好, jsx 裏的代碼類型提示應有盡有, 能夠很大的提高開發效率以及減小 bug. 覺得在 VUE 也能獲得這樣好的體驗.html

可是....前端

VUE 雖然也能夠支持 JSX, 官方仍是推薦使用模板渲染, 而模板裏壓根沒有代碼提示, 因此 ts 的功力就廢了一半(剩下的一半還在 script 裏), 具體的討論請參考vue

準備

前戲差很少了, 開始步入正題.java

  1. 安裝 vue cli 3.x這個腳手架能夠快速啓動項目 yarn global add @vue/cli 推薦全局安裝.node

  2. 在任意文件夾下 vue create .. 會在當前文件夾中注入初始項目腳手架, 包含 webpack, babel, ts, lint, jest, jsdoc 等一整套模板react

  3. 使用 ts 會默認使用 vue-property-decorator 這個庫 github, 這個庫用裝飾器的寫法, 將不少 js 中繁雜的配置抽象出來, 一開始不習慣. 習慣了能夠大大提升效率的.linux

vue-property-decorator的食用方法

@Component(options)

這一個是與另外一個 vue 的庫 vue-class-component同樣的用法. 這個裝飾器庫源自 class 庫, 只是再封裝了一層, 使代碼更爲簡潔明瞭. options 裏面須要配置 decorator 庫不支持的屬性, 哪些是不支持的呢? 那就請看徹底文, 凡是沒寫的都是不支持的. 好比components, filters, directiveswebpack

components

表示該組件引入了哪些子組件git

<template>
  <div id="app">
    <HelloWorld />
  </div>
</template>

<script lang="ts"> @Component({ components: { HelloWorld, // 聲明子組件的引用 } }) export default class App extends Vue {} </script>
複製代碼

filters

filter 表示對數據的篩選, 跟 linux 中的管道符十分類似, 數據經過 filter 進行處理變成新的數據.

注意, 在這裏配置時必定要使用 filters, 不要忘了 s, 不然不會報錯可是也沒做用.

<template>
  <div>{{msg | addWorld}}</div>
</template>

<script lang="ts"> @Component({ filters: { addWorld: (value: string) => `${value} world`, }, }) export default class App extends Vue { private msg = 'Hello' // filter 以後顯示 hello world } </script>
複製代碼

directives

具體的介紹能夠看 Vue 的官方介紹. 簡單來講就是 DOM 節點在一些特定鉤子觸發時添加一些額外的功能

鉤子函數有:

  • bind 指令綁定到元素時調用, 能夠進行一次性設置
  • inserted 綁定元素被插入到父節點時調用
  • update VNode 更新時調用
  • componentUpdated VNode 及子 VNode 所有更新後調用
  • unbind 元素解綁時調用

若是bind 和 update 時調用同樣能夠進行簡寫, 不用指定某個鉤子

鉤子函數參數:

  • el 對應綁定的 DOM
  • binding
    • name 指令名 v-demo="1+1" 爲 "demo"
    • value 綁定的值 v-demo="1+1" 爲 2
    • oldValue 在 update componentUpdated 可用
    • expression 字符串形式的表達式 v-demo="1+1" 爲 "1+1"
    • arg 指令的參數 v-demo:foo 爲 'foo', 注意要在 modifier 前使用 arg, 否則會將 arg 做爲 modifier 的一部分, 如v-demo.a:foo arg 爲 undefined, modifier 爲{'a:foo': true}
    • modifiers 包含修飾符的對象 好比 v-demo.a 這個值爲 {a:true}
  • vnode vue 的虛擬節點, 可參考源碼查看可用屬性
  • oldVnode 上一個虛擬節點(update 和 componentUpdated 可用)

看個簡單的實例:

<template>
  <span v-demo:foo.a="1+1">test</span>
</template>

<script lang="ts"> @Component({ directives: { demo: { bind(el, binding, vnode) { console.log(`bindingName: ${binding.name}, value: ${binding.value}, args: ${binding.arg}, expression: ${binding.expression}`); // bindingName: demo, value: 2, args: foo, expression: 1+1 console.log('modifier:', binding.modifiers); // {a:true}, 沒法轉爲 primitive, 因此單獨打印 }, }, demoSimplify(el, binding, vnode) { // do stuff }, }, }) export default class App extends Vue {} </script>
複製代碼

@Prop()

父子組件傳遞數據 props的修飾符, 參數能夠傳

  • Constructor 例如String, Number, Boolean
  • Constructor[], 構造函數的隊列, 類型在這隊列中便可
  • PropOptions
    • type 類型不對會報錯 Invalid prop: type check failed for prop "xxx". Expected Function, got String with value "xxx".
    • default 若是父組件沒有傳的話爲該值, 注意只能用這一種形式來表示默認值, 不能@Prop() name = 1來表示默認值 1, 雖然看起來同樣, 可是會在 console 裏報錯, 不容許修改 props 中的值
    • required 沒有會報錯 [Vue warn]: Missing required prop: "xxx"
    • validator 爲一個函數, 參數爲傳入的值, 好比(value) => value > 100

父組件:

<template>
  <div id="app">
    <PropComponent :count='count' />
  </div>
</template>
<script lang="ts"> @Component({ components: {PropComponent} }) class Parent extends Vue { private count = 101 } </script>
複製代碼

子組件:

<template>
  <div>{{count}}</div>
</template>

<script lang="ts"> @Component export default class PropsComponent extends Vue { @Prop({ type: Number, validator: (value) => { return value > 100; }, required: true }) private count!: string // !表示有值, 不然 ts 會告警未初始化 } </script>
複製代碼

@PropSync()

與 Prop 的區別是子組件能夠對 props 進行更改, 並同步給父組件,

子組件:

<template>
  <div>
    <p>{{count}}</p>
    <button @click="innerCount += 1">increment</button>
  </div>
</template>

<script lang="ts"> @Component export default class PropSyncComponent extends Vue { @PropSync('count') private innerCount!: number // 注意@PropSync 裏的參數不能與定義的實例屬性同名, 由於仍是那個原理, props 是隻讀的. } </script>
複製代碼

父組件: 注意父組件裏綁定 props 時須要加修飾符 .sync

<template>
    <PropSyncComponent :count.sync="count"/>
</template>
<script lang="ts"> @Component({ components: {PropSyncComponent} }) export default class PropSyncComponent extends Vue { @PropSync('count') private innerCount!: number // 注意@PropSync 裏的參數不能與定義的實例屬性同名, 由於仍是那個原理, props 是隻讀的. } </script>
複製代碼

也可結合 input 元素的 v-model 綁定數據, 實時更新. 由讀者自行實現.

@Watch

監聽屬性發生更改時被觸發. 可接受配置參數 options

  • immediate?: boolean 是否在偵聽開始以後當即調用該函數
  • deep?: boolean 是否深度監聽.
<template>
  <div>
    <button @click="innerName.name.firstName = 'lorry'">change deeper</button>
    <button @click="innerName.name = 'lorry'">change deep</button>
  </div>
</template>
<script lang="ts"> @Component export default class PropSyncComponent extends Vue { private person = { name: { firstName: 'jiang' } } @Watch('person', { deep: true, }) private firstNameChange(person: number, oldPerson:number) { console.log(`count change from${oldName.name.first}to: ${oldName.name.}`); } } </script>
複製代碼

@Emit

  • 接受一個參數 event?: string, 若是沒有的話會自動將 camelCase 轉爲 dash-case 做爲事件名.
  • 會將函數的返回值做爲回調函數的第二個參數, 若是是 Promise 對象,則回調函數會等 Promise resolve 掉以後觸發.
  • 若是$emit 還有別的參數, 好比點擊事件的 event , 會在返回值以後, 也就是第三個參數.

子組件:

<template>
  <div>
    <button @click="emitChange">Emit!!</button>
  </div>
</template>

<script lang="ts"> @Component export default class EmitComponent extends Vue { private count = 0; @Emit('button-click') private emitChange() { this.count += 1; return this.count; } } </script>
複製代碼

父組件, 父組件的對應元素上綁定事件便可:

<template>
  <EmitComponent v-on:button-click='listenChange'/>
</template>

<script lang="ts"> @Component({ components: { EmitComponent, }, }) export default class App extends Vue { private listenChange(value: number, event: any) { console.log(value, e); } } </script>
複製代碼

@Ref

跟 react 中的同樣, ref 是用於引用實際的 DOM 元素或者子組件.應儘量避免直接使用, 但若是不得不用 ref 比 document 拿要方便不少, 參數傳一個字符串refKey?:string, 注意這裏若是省略傳輸參數, 那麼會自動將屬性名做爲參數, 注意與@Emit的區別, @Emit在不傳參數的狀況下會轉爲 dash-case, 而 @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; private value = 'lorry' private mounted() { console.log(this.inputName); // <input type="text"> // do stuff to ref } } </script>
複製代碼

@Provide/@inject && @ProvideReactive/@InjectReactive

其本質是轉換爲 injectprovide, 這是 vue 中元素向更深層的子組件傳遞數據的方式.二者須要一塊兒使用.與 react 的 context 十分的像.

任意代的子組件:

<template>
  <span>Inject deeper: {{bar}}</span>
</template>

<script lang="ts"> @Component export default class InjectComponent extends Vue { @Inject() private bar!: string private mounted() { console.log(this.bar); } } </script>
複製代碼

任意祖先元素:

<script> export default class App extends Vue { @Provide() private bar = 'deeper lorry' } </script>
複製代碼

方便不少, 若是爲了不命名衝突, 可使用 ES6 的 Symbol 特性做爲 key, 以祖先元素舉例:

須要注意的是避免相互引用的問題, symbol 的引用最好放到組件外單獨有個文件存起來.

export const s = Symbol()
複製代碼

父組件:

<script> export default class App extends Vue { @Provide(s) private bar = 'deeper lorry' } </script>
複製代碼

子組件:

<script> @Component export default class App extends Vue { @Inject(s) private baz = 'deeper lorry' } </script>
複製代碼

@ProvideReactive/@InjectReactive 顧名思義就是響應式的注入, 會同步更新到子組件中.好比下例能夠實如今 input 中的輸入實時注入到子組件中 父組件

<template>
  <div id="app">
    <input type="text" v-model="bar">
    <InjectComponent />
  </div>
</template>
<script> @Component({ InjectComponent }) export default class App extends Vue { @ProvideReactive(s) private bar = 'deeper lorry' } </script>
複製代碼

子組件:

<script> @Component export default class InjectComponent extends Vue { @InjectReactive(s) private baz!: string } </script>
複製代碼

以上爲文檔中羅列的用法,之後項目過程當中遇到了別的會回來更新.

敬請指正.

相關文章
相關標籤/搜索