Vue 中 $on $once $off $emit 詳細分析,以及使用

影的記憶發表於2021-12-02

vue的 $on,$emit,$off,$once

Api 中的解釋:

  • $on(eventName:string|Array, callback) 監聽事件

    • 監聽當前例項上的自定義事件。事件可以由 vm.$emit 觸發。回撥函式會接收所有傳入事件觸發函式的額外引數。
  • $once(eventName: string, callback) 監聽事件

    • 監聽一個自定義事件,但是隻觸發一次。一旦觸發之後,監聽器就會被移除。
  • $off(eventName: string| Array, callback) 移除自定義事件監聽器

      1. 如果沒有提供引數,則移除所有的事件監聽器;
    
      2. 如果只提供了事件,則移除該事件所有的監聽器;
    
      3. 如果同時提供了事件與回撥,則只移除這個回撥的監聽器。
    
  • 使用 $emit(eventName, [..args]) 觸發事件

    • 觸發當前例項上的事件。附加引數都會傳給監聽器回撥

$on 實現原理

Vue.prototype.$on = function (event, fn) {
    var vm = this;
    if (Array.isArray(event)) {
    for (var i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn);
    }
    } else {
    (vm._events[event] || (vm._events[event] = [])).push(fn);
    // optimize hook:event cost by using a boolean flag marked at registration
    // instead of a hash lookup
    if (hookRE.test(event)) {
        vm._hasHookEvent = true;
    }
    }
    return vm
};

$once 實現原理

Vue.prototype.$once = function (event, fn) {
      var vm = this;
      function on () {
        vm.$off(event, on);
        fn.apply(vm, arguments);
      }
      on.fn = fn;
      vm.$on(event, on);
      return vm
    };

$off

Vue.prototype.$off = function (event, fn) {
      var vm = this;
      // all
      if (!arguments.length) {
        vm._events = Object.create(null);
        return vm
      }
      // array of events
      if (Array.isArray(event)) {
        for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
          vm.$off(event[i$1], fn);
        }
        return vm
      }
      // specific event
      var cbs = vm._events[event];
      if (!cbs) {
        return vm
      }
      if (!fn) {
        vm._events[event] = null;
        return vm
      }
      // specific handler
      var cb;
      var i = cbs.length;
      while (i--) {
        cb = cbs[i];
        if (cb === fn || cb.fn === fn) {
          cbs.splice(i, 1);
          break
        }
      }
      return vm
    };

$emit 實現原理

Vue.prototype.$emit = function (event) {
      var vm = this;
      {
        var lowerCaseEvent = event.toLowerCase();
        if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
          tip(
            "Event \"" + lowerCaseEvent + "\" is emitted in component " +
            (formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " +
            "Note that HTML attributes are case-insensitive and you cannot use " +
            "v-on to listen to camelCase events when using in-DOM templates. " +
            "You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"."
          );
        }
      }
      var cbs = vm._events[event];
      if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs;
        var args = toArray(arguments, 1);
        var info = "event handler for \"" + event + "\"";
        for (var i = 0, l = cbs.length; i < l; i++) {
          invokeWithErrorHandling(cbs[i], vm, args, vm, info);
        }
      }
      return vm
    };

用法:監聽當前例項上的自定義事件。事件可以由 vm.$emit 觸發。回撥函式會接收所有傳入事件觸發函式的額外引數。

應用

  • 多次繫結,$on, $once 將多次呼叫,$off 呼叫一次將解綁所有相同名字的$on, $once 監聽事件
<template>
  <div class="hello">
    <button @click="onEmit">OnEmit</button>
    <button @click="onOff">OnOff</button>
    <button @click="onOn">on</button>
  </div>
</template>

<script>
export default {
  name: 'Test',
  mounted() {

  },
  methods: {
    onEmit() {
      this.$emit('onTest', 1);
      this.$emit('onTest', 2)
      this.$emit('onTest', 3)
      this.$emit('onTest', 4)
      this.$emit('onTestOnce', 5);
      this.$emit('onTestOnce', 6);
      this.$emit('onTestOnce', 7);
      this.$emit('onTestOnce', 8);
    },
    onOff() {
      this.$off('onTest');
    },
    onOn(){
      this.$on('onTest', (args) => {
        console.log(args)
      })
      this.$once('onTestOnce', (args) => {
        console.log(args)
      })
    }
  }
}
</script>

子元件到父元件通訊

<hello @pfn="parentFn"></hello>

<script>
  Vue.component('hello', {
    template: '<button @click="fn">按鈕</button>',
    methods: {
      // 子元件:通過$emit呼叫
      fn() {
        this.$emit('pfn', '這是子元件傳遞給父元件的資料')
      }
    }
  })
  new Vue({
    methods: {
      // 父元件:提供方法
      parentFn(data) {
        console.log('父元件:', data)
      }
    }
  })
</script>

非父子元件通訊

  • 元件 A.vue
<template>
  <div @click="onAon">
    元件a
  </div>
</template>
<script>
// import bus from "@/components/bus";
export default {
  name: 'A',
  methods: {
    onAon() {
      this.$bus.$emit('bTest')
    }
  }
}
</script>

元件B.vue

<template>
  <div>
    b 元件
  </div>
</template>
<script>
// import bus from "@/components/bus";
export default {
  mounted() {
    this.$bus.$on('bTest', () => {
      console.log('---- 觸發b元件監聽 ------')
    })
  }
}
</script>

  • bus.js
import Vue from "vue";

let bus = new Vue();
export default bus;

注:可以將bus 全域性引入

相關文章