@Component(options)
這一個(gè)是與另一個(gè) vue 的庫 vue-class-component 一樣的用法. 這個(gè)裝飾器庫源自 class 庫, 只是再封裝了一層, 使代碼更為簡潔明了. options 里面需要配置 decorator 庫不支持的屬性, 哪些是不支持的呢?
那就請看完全文, 凡是沒寫的都是不支持的. 比如components, filters, directives等
- components
表示該組件引入了哪些子組件
<template>
<div id="app">
<HelloWorld />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({
components: {
HelloWorld, // 聲明子組件的引用
}
})
export default class App extends Vue {}
</script>
- filters
filter 表示對數(shù)據(jù)的篩選, 跟 linux 中的管道符十分相似, 數(shù)據(jù)通過 filter 進(jìn)行處理變成新的數(shù)據(jù).注意, 在這里配置時(shí)一定要使用 filters, 不要忘了 s, 否則不會報(bào)錯(cuò)但是也沒作用海洼。
<template>
<div>{{msg | filterA}}</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({
filters: {
addWorld: (value: string) => `${value} world`,
},
})
export default class App extends Vue {
private msg = 'Hello' // filter 之后顯示 hello world
}
</script>
- directives
具體的介紹可以看 Vue 的官方介紹 簡單來說就是 DOM 節(jié)點(diǎn)在一些特定鉤子觸發(fā)時(shí)添加一些額外的功能瞎领。
鉤子函數(shù)有:
- bind 指令綁定到元素時(shí)調(diào)用, 可以進(jìn)行一次性設(shè)置
- inserted 綁定元素被插入到父節(jié)點(diǎn)時(shí)調(diào)用
- update VNode 更新時(shí)調(diào)用
- componentUpdated VNode 及子 VNode 全部更新后調(diào)用
- unbind 元素解綁時(shí)調(diào)用
如果bind 和 update 時(shí)調(diào)用一樣可以進(jìn)行簡寫, 不用指定某個(gè)鉤子
鉤子函數(shù)參數(shù):
el 對應(yīng)綁定的 DOM
binding
- name 指令名 v-demo="1+1" 為 "demo"
- value 綁定的值 v-demo="1+1" 為 2
- oldValue 在 update componentUpdated 可用
- expression 字符串形式的表達(dá)式 v-demo="1+1" 為 "1+1"
- arg 指令的參數(shù) v-demo:foo 為 'foo', 注意要在 modifier 前使用 arg, 不然會將 arg 作為 modifier 的一部分, 如v-demo.a:foo arg 為 undefined, modifier 為{'a:foo': true}
- modifiers 包含修飾符的對象 比如 v-demo.a 這個(gè)值為 {a:true}
vnode vue 的虛擬節(jié)點(diǎn), 可參考源碼查看可用屬性
oldVnode 上一個(gè)虛擬節(jié)點(diǎn)(update 和 componentUpdated 可用)
看個(gè)簡單的實(shí)例:
<template>
<span v-demo:foo.a="1+1">test</span>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@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}, 無法轉(zhuǎn)為 primitive, 所以單獨(dú)打印
},
},
demoSimplify(el, binding, vnode) {
// do stuff
},
},
})
export default class App extends Vue {}
</script>
@Prop()
父子組件傳遞數(shù)據(jù) props的修飾符
參數(shù)可以傳
Constructor 例如String, Number, Boolean
Constructor[], 構(gòu)造函數(shù)的隊(duì)列, 類型在這隊(duì)列中即可
-
PropOptions
- type 類型不對會報(bào)錯(cuò) Invalid prop: type check failed for prop "xxx". Expected Function, got String with value "xxx".
- default 如果父組件沒有傳的話為該值, 注意只能用這一種形式來表示默認(rèn)值, 不能@Prop() name = 1來表示默認(rèn)值 1, 雖然看起來一樣, 但是會在 console 里報(bào)錯(cuò), 不允許修改 props 中的值
- required 沒有會報(bào)錯(cuò) [Vue warn]: Missing required prop: "xxx"
- validator 為一個(gè)函數(shù), 參數(shù)為傳入的值, 比如(value) => value > 100
父組件:
<template>
<div id="app">
<PropComponent :count="count" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component
class Parent extends Vue {
private count = 101;
}
</script>
子組件:
<template>
<div>{{ count }}</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"
@Component
export default class PropsComponent extends Vue {
// !表示有值, 否則 ts 會告警未初始化 }
@Prop({
type: Number,
validator: value => {
return value > 100
},
required: true
})
private count!: string
}
</script>
@PropSync() 與 Prop 的區(qū)別是子組件可以對 props 進(jìn)行更改, 并同步給父組件,
子組件:
<template>
<div>
<p>{{count}}</p>
<button @click="innerCount += 1">increment</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, PropSync } from "vue-property-decorator"
@Component
export default class PropSyncComponent extends Vue {
// 注意@PropSync 里的參數(shù)不能與定義的實(shí)例屬性同名, 因?yàn)檫€是那個(gè)原理, props 是只讀的
@PropSync('count') private innerCount!: number
}
</script>
父組件: 注意父組件里綁定 props 時(shí)需要加修飾符 .sync
<template>
<PropSyncComponent :count.sync="count" />
</template>
<script lang="ts">
import { Component, Vue, PropSync } from "vue-property-decorator"
@Component({ components: PropSyncComponent })
export default class PropSyncComponent extends Vue {
// 注意@PropSync 里的參數(shù)不能與定義的實(shí)例屬性同名, 因?yàn)檫€是那個(gè)原理, props 是只讀的.
@PropSync('count') private innerCount!: number
}
</script>
也可結(jié)合 input 元素的 v-model 綁定數(shù)據(jù), 實(shí)時(shí)更新. 由讀者自行實(shí)現(xiàn).
@Watch監(jiān)聽屬性發(fā)生更改時(shí)被觸發(fā).
可接受配置參數(shù) options
- immediate?: boolean 是否在偵聽開始之后立即調(diào)用該函數(shù)
- deep?: boolean 是否深度監(jiān)聽.
<template>
<div>
<button @click="person.name.firstName = '修'">change deeper</button>
<button @click="person.name = '修'">change deep</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
@Component
export default class PropSyncComponent extends Vue {
private person = { name: { firstName: '修' } }
@Watch('person', { deep: true })
private firstNameChange(person: object, oldPerson: object) {
console.log(person, oldPerson)
}
}
</script>
@Emit
- 接受一個(gè)參數(shù)
event?: string
, 如果沒有的話會自動將 camelCase 轉(zhuǎn)為 dash-case 作為事件名. - 會將函數(shù)的返回值作為回調(diào)函數(shù)的第二個(gè)參數(shù), 如果是 Promise 對象,則回調(diào)函數(shù)會等 Promise resolve 掉之后觸發(fā).
- 如果$emit 還有別的參數(shù), 比如點(diǎn)擊事件的 event , 會在返回值之后, 也就是第三個(gè)參數(shù).
子組件:
<template>
<div>
<button @click="emitChange">Emit!!</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Emit } from "vue-property-decorator";
@Component
export default class EmitComponent extends Vue {
private count = 0
@Emit("button-click") private emitChange() {
this.count += 1
return this.count
}
}
</script>
父組件, 父組件的對應(yīng)元素上綁定事件即可:
<template>
<EmitComponent v-on:button-click="listenChange" />
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator"
@Component({ components: { EmitComponent } })
export default class App extends Vue {
private listenChange(value: number, event: any) {
console.log(value, e)
}
}
</script>
@Ref 跟 react 中的一樣, ref 是用于引用實(shí)際的 DOM 元素或者子組件.應(yīng)盡可能避免直接使用, 但如果不得不用 ref 比 document 拿要方便很多, 參數(shù)傳一個(gè)字符串refKey?:string, 注意這里如果省略傳輸參數(shù), 那么會自動將屬性名作為參數(shù), 注意與@Emit的區(qū)別, @Emit在不傳參數(shù)的情況下會轉(zhuǎn)為 dash-case, 而 @Ref不會轉(zhuǎn), 為原屬性名
<template>
<div>
<span>Name:</span>
<input type="text" v-model="value" ref="name" />
</div>
</template>
<script lang="ts">
import { Component, Vue, Ref } from 'vue-property-decorator'
@Component
export default class RefComponent extends Vue {
@Ref('name') readonly name!: string
private value = '修'
private mounted() {
console.log(this.name)
}
}
</script>
@Provide/@inject && @ProvideReactive/@InjectReactive
其本質(zhì)是轉(zhuǎn)換為 inject 和 provide, 這是 vue 中元素向更深層的子組件傳遞數(shù)據(jù)的方式.兩者需要一起使用.與 react 的 context 十分的像
任意代的子組件:
<template>
<span>Inject deeper: {{ bar }}</span>
</template>
<script lang="ts">
import { Component, Vue, Inject } from "vue-property-decorator";
@Component
export default class InjectComponent extends Vue {
@Inject()
private bar!: string
private mounted() {
console.log(this.bar)
}
}
</script>
任意祖先元素:
<script lang="ts">
import { Component, Vue, Provide } from "vue-property-decorator"
export default class App extends Vue {
@Provide()
private bar = "deeper 修"
}
</script>
方便很多, 如果為了避免命名沖突, 可以使用 ES6 的 Symbol 特性作為 key, 以祖先元素舉例:
需要注意的是避免相互引用的問題, symbol 的引用最好放到組件外單獨(dú)有個(gè)文件存起來.
export const s = Symbol()
父組件:
<script lang="ts">
import { Component, Vue, Provide } from "vue-property-decorator"
export const s = Symbol()
@Component
export default class App extends Vue {
@Provide(s)
private bar = "deeper 修"
}
</script>
子組件:
<script lang="ts">
import { Component, Vue, Inject } from "vue-property-decorator";
import { s } from 'xxx'
@Component
export default class App extends Vue {
@Inject(s) private baz = "deeper 修"
}
</script>
@ProvideReactive/@InjectReactive 顧名思義就是響應(yīng)式的注入, 會同步更新到子組件中.比如下例可以實(shí)現(xiàn)在 input 中的輸入實(shí)時(shí)注入到子組件中 父組件
<template>
<div id="app">
<input type="text" v-model="bar" />
<InjectComponent />
</div>
</template>
<script lang="ts">
import { Component, Vue, ProvideReactive } from "vue-property-decorator"
export const s = Symbol();
@Component({ InjectComponent })
export default class App extends Vue {
@ProvideReactive(s) private bar = "deeper 修"
}
</script>
子組件:
<script lang="ts">
import { Component, Vue, InjectReactive } from "vue-property-decorator"
@Component
export default class InjectComponent extends Vue {
@InjectReactive(s) private baz!: string
}
</script>
@Model
Vue組件提供model: {prop?: string, event?: string}
讓我們可以定制prop
和event
.
默認(rèn)情況下萝风,一個(gè)組件上的v-model 會把 value用作 prop且把 input用作 event开泽,但是一些輸入類型比如單選框和復(fù)選框按鈕可能想使用 value prop來達(dá)到不同的目的。使用model選項(xiàng)可以回避這些情況產(chǎn)生的沖突。
- 下面是Vue官網(wǎng)的例子
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
}, props: { // this allows using the `value` prop for a different purpose
value: String,
checked: { // use `checked` as the prop which take the place of `value`
type: Number, default: 0
}
}, // ...
})
<template>
<my-checkbox v-model="foo" value="some value"></my-checkbox>
</template>
上述代碼相當(dāng)于:
<template>
<my-checkbox :checked="foo" @change="val => { foo = val }" value="some value" />
</template>
即foo雙向綁定的是組件的checke, 觸發(fā)雙向綁定數(shù)值的事件是change
使用vue-property-decorator提供的@Model改造上面的例子.
<script lang="ts">
import { Vue, Component, Model } from "vue-property-decorator"
@Component
export class myCheck extends Vue {
@Model("change", { type: Boolean }) checked!: boolean
}
</script>
- 總結(jié), @Model()接收兩個(gè)參數(shù), 第一個(gè)是event值, 第二個(gè)是prop的類型說明, 與@Prop類似, 這里的類型要用JS的. 后面在接著是prop和在TS下的類型說明.