vue中在当前组件中定义的全局变量怎么在methods中使用_Vue原理解析(十):搞懂事件API原理及在组件库中的妙用...
在vue內部初始化時會為每個組件實例掛載一個this._events私有的空對象屬性:
vm._events = Object.create(null) // 沒有__proto__屬性這個里面存放的就是當前實例上的自定義事件集合,也就是自定義事件中心,它存放著當前組件所有的自定義事件。和自定義事件相關的API分為以下四個:this.$on、this.$emit、this.$off、this.$once,它們會往這個事件中心中添加、觸發、移除對應的自定義事件,從而組成了vue的自定義事件系統,接下來看下它們都是怎么實現的。 * ### this.$on
描述:監聽當前實例上的自定義事件。事件可以由vm.$emit觸發,回調函數會接收所有傳入事件觸發函數的額外參數。export default {created() {this.$on('test', res => {console.log(res) })},methods: {handleClick() {this.$emit('test', 'hello-vue~')}} }以上示例首先在created鉤子內往當前組件實例的事件中心_events中添加一個名為test的自定義事件,第二個參數為該自定義事件的回調函數,而觸發handleClick這個方法后,就會在事件中心中嘗試找到test自定義事件,觸發它并傳遞給回調函數hello-vue~這個字符串,從而打印出來。我們來看下$on的實現:
Vue.prototype.$on = function (event, fn) {const hookRE = /^hook:/ //檢測自定義事件名是否是hook:開頭const vm = thisif (Array.isArray(event)) { // 如果第一個參數是數組for (let i = 0; i < event.length; i++) {this.$on(event[i], fn) // 遞歸}} else {(vm._events[event] || (vm._events[event] = [])).push(fn)// 如果有對應事件名就push,沒有創建為空數組然后pushif (hookRE.test(event)) { // 如果是hook:開頭vm._hasHookEvent = true // 標志位為true}}return vm }以上就是$on的實現了,它接受兩個參數,自定義事件名event和對應的回調函數fn。主要就是往事件中心_events下掛載對應的event事件名key,而事件名對應的key又是一個數組形式,這樣相同事件名的回調會在一個數組之內。而接下來的_hasHookEvent標志位表示是否監聽組件的鉤子函數,這個之后示例說明。
- this.$emit
而$emit的實現會更好理解些,首先從事件中心中找到event對應的回調集合,然后將$emit其余參數轉為args數組,最后挨個執行回調集合內的回調并傳入args。通過這么一對樸實的API可以幫我們理解三件小事:
1. 理解自定義事件原理app.vue <template><child-component @test='handleTest' /> </template> export default {methods: {handleTest(res) {console.log(res)}} }----------------------------------------child.vue <template><button @click='onClick'>btn</button> </template> export default {methods: {onClick() {this.$emit('test', 'hello-vue~')}} }以上是父子組件通過自定義事件通信,想必大家非常熟悉。自定義事件的實現原理和通常解釋的會不同,它們的原理是父組件在經過編譯模板后,會將定義在子組件上的自定義事件test及其回調handleTest通過$on添加到子組件的事件中心中,當子組件通過$emit觸發test自定義事件時,會在它的事件中心中去找test,找到后傳遞hello-vue~給回調函數并執行,不過因為回調函數handleTest是在父組件作用域內定義的,所以看起來就像是父子組件之間通信般。
2. 監聽組件的鉤子函數也就是$on內自定義事件名之前是hook:的情況,可以監聽組件的鉤子函數觸發:
app.vue <template><child-component @hook:created='handleHookEvent' /> </template>以上示例為當子組件的created鉤子觸發時,就觸發父組件內定義的handleHookEvent回調。接下來讓我們再看一個官網的示例,使用這個特性如何幫我們寫出更優雅的代碼:
監聽組件鉤子之前: mounted () {this.picker = new Pikaday({ // Pikaday是一個日期選擇庫field: this.$refs.input,format: 'YYYY-MM-DD'}) }, beforeDestroy () { // 銷毀日期選擇器this.picker.destroy() }監聽組件鉤子之后: mounted() {this.attachDatepicker('startDateInput')this.attachDatepicker('endDateInput') // 同時為兩個input添加日期選擇 }, methods: {attachDatepicker(refName) { // 封裝為一個方法const picker = new Pikaday({ // Pikaday是一個日期選擇庫field: this.$refs[refName], // 為input添加日期選擇format: 'YYYY-MM-DD'})this.$once('hook:beforeDestroy', () => { // 監聽beforeDestroy鉤子picker.destroy() // 銷毀日期選擇器}) // $once和$on類似,只是只會觸發一次} }首先不用在當前實例下掛載一個額外的屬性,其次可以封裝為一個方法,復用更方便。
3. 不借助vuex跨組件通信再開發組件庫時,因為都是獨立的組件,從而引入vuex這種強依賴是不現實的,而且很多時候是用插槽來放置子組件,所以子組件的位置、嵌套、數量并不會確定,從而在組件庫內完成跨組件的通信就尤為重要。
通過接下來的示例介紹組件庫中會運用到的一種,使用$on和$emit來實現跨組件通信,子組件通過父組件的name屬性找到對應的實例,找到后使用$emit觸發父組件的自定義事件,而在這之前父組件已經使用$on完成了自定義事件的添加:
export default {methods: { // 混入mixin使用dispatch(componentName, eventName, params) {let parent = this.$parent || this.$root // 找父組件let name = parent.$options.name // 父組件的name屬性while (parent && (!name || name !== componentName)) { // 和傳入的componentName進行匹配parent = parent.$parent // 一直向上查找if (parent) {name = parent.$options.name // 重新賦值name}}if (parent) { // 找到匹配的組件實例parent.$emit.apply(parent, [eventName].concat(params)) // $emit觸發自定義事件}}} }接下來介紹表單驗證組件內的使用案例:
不知道大家是否對這種表單驗證好奇過,為什么點一下提交,就可以將所有的表單項全部做驗證,接下來筆者試著寫一個極簡的表單驗證組件來說明它的原理。這里會有兩個組件,一個是iForm為整個表單,一個是iFormItem為其中的某個表單項:
iForm組件:<template><div> <slot /> </div> // 只有一個插槽 </template><script> export default {name: "iForm", // 組件名很重要data() {return {fields: [] // 收集所有表單項的集合};},created() {this.$on("on-form-item-add", field => { // $on必須得比$emit先執行,因為要先添加嘛this.fields.push(field) // 添加到集合內});},methods: {validataAll() { // 驗證所有的接口方法this.fields.forEach(item => {item.validateVal() // 執行每個表單項內的validateVal方法});}} }; </script>模板只有一個slot插槽,這個組件主要是做兩件事,將所有的表單項的實例收集到fields內,提供一個可以驗證所有表單項的方法validataAll,然后看下iFormItem組件:
<template><div><input v-model="curValue" style="border: 1px solid #aaa;" /><span style="color: red;" v-show="showTip">輸入不能為空</span></div> </template><script> import emitter from "./emitter" // 引入之前的dispatch方法export default {name: "iFormItem",mixins: [emitter], // 混入data() {return {curValue: "", // 表單項的值showTip: false // 是否驗證通過};},created() {this.dispatch("iForm", "on-form-item-add", this) // 將當前實例傳給iForm組件},methods: {validateVal() { // 某個表單項的驗證方法if (this.curValue === "") { // 不能為空this.showTip = true // 驗證不通過}}} }; </script>看到這里我們知道了原來這種表單驗證原理是將每個表單項的實例傳入給iForm,然后在iForm內遍歷的執行每個表單項的驗證方法,從而可以一次性驗證完所有的表單項。表單驗證調用方式:
<template><div><i-form ref='form'> // 引用<i-form-item /><i-form-item /><i-form-item /><i-form-item /><i-form-item /></i-form><button @click="submit">提交</button></div> </template><script> import iForm from "./form" import iFormItem from "./form-item"export default {methods: {submit() {this.$refs['form'].validataAll() // 驗證所有}},components: {iForm, iFormItem} }; </script>這里就使用了$on和$emit這么一對API,通過組件的名稱去查找組件實例,不論嵌套以及數量,然后使用事件API去跨組件傳遞參數。
注意點:當$on和$emit配合使用時,$on要優先與$emit執行。因為首先要往實例的事件中心去添加事件,才能被觸發。- this.$off
- 如果沒有提供參數,則移除所有的事件監聽器;
- 如果只提供了事件,則移除該事件所有的監聽器;
- 如果同時提供了事件與回調,則只移除這個回調的監聽器。
知道了這個API的調用方式之后,接下來看下$off的實現方式:
Vue.prototype.$off = function (event, fn) {const vm = thisif (!arguments.length) { // 如果沒有傳遞參數vm._events = Object.create(null) // 重置事件中心return vm}if (Array.isArray(event)) { // event如果是數組for (let i = 0, l = event.length; i < l; i++) {vm.$off(event[i], fn) // 遞歸清空}return vm}if (!fn) { // 只傳遞了事件名沒回調vm._events[event] = null // 清空對應所有的回調return vm}const cbs = vm._events[event] // 獲取回調集合let cblet i = cbs.lengthwhile (i--) {cb = cbs[i] // 回調集合里的每一項if (cb === fn || cb.fn === fn) { // cb.fn為$once時掛載的cbs.splice(i, 1) // 找到對應的回調,從集合內移除break}}return vm }也是分為了三種情況,根據參數的不同做分別處理。 * ### this.$once
描述:監聽一個自定義事件,但是只觸發一次,在第一次觸發之后移除監聽器。效果和$on是類似的,只是說觸發一次之后會從事件中心中移除。所以它的實現思路也很好理解,首先通過$on實現功能,當觸發之后從事件中心中移除這個事件。來看下它的實現原理:
Vue.prototype.$once = function (event, fn) {const vm = thisfunction on () {vm.$off(event, on)fn.apply(vm, arguments)}on.fn = fn // 回調掛載到on下,移除時好做判斷vm.$on(event, on) // 將on添加到事件中心中return vm }首先將回調fn掛載到on函數下,將on函數注冊到事件中心去,觸發自定義事件時首先會在$emit內執行on函數,在on函數內執行$off將on函數移除,然后執行傳入的fn回調。這個時候事件中心沒有了on函數,回調函數也執行了一次,完成$once功能~
事件API總結:$on往事件中心添加事件;$emit是觸發事件中心里的事件;$off是移除事件中心里的事件;$once是觸發一次事件中心里的事件。哪怕是如此不顯眼的API,再理解了它們的實現原理后,也能讓我們再更多場景更好的使用它們~最后按照慣例我們還是以一道vue可能會被問到的面試題作為本章的結束(想不到事件相關特別好的題目~)。
面試官微笑而又不失禮貌的問道:- 說下自定義事件的機制。
懟回去:
- 子組件使用this.$emit觸發事件時,會在當前實例的事件中心去查找對應的事件,然后執行它。不過這個事件回調是在父組件的作用域里定義的,所以$emit里的參數會傳遞給父組件的回調函數,從而完成父子組件通信。
順手點個贊或關注唄,找起來也方便~
胡成:你可能會用的上的一個vue功能組件庫,持續完善中...?zhuanlan.zhihu.com總結
以上是生活随笔為你收集整理的vue中在当前组件中定义的全局变量怎么在methods中使用_Vue原理解析(十):搞懂事件API原理及在组件库中的妙用...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: caffe教程翻译:Alex’s CIF
- 下一篇: 校验json格式_不来学一下Spring