Как я могу заемитить событие из директивы, щелкнув за пределами компонента?

128
29 июля 2019, 22:40

Я не могу понять, как испустить событие из директивы

Это мой шаблон, где я пытаюсь вызвать метод:

<tooltip v-click-outside="clickEvent" v-on:emitedEvent="clickEvent"></tooltip>

Из v-click-outside="clickEvent" я получаю:

Property or method "clickEvent" is not defined on the instance but referenced during render.

Код:

export default {
  data() {
    return {
    }
  },
  methods: {
    clickEvent: function () {
      console.log('click')
    }
  },
}
Vue.directive('click-outside', {
  bind: function (el, binding, vnode) {
    el.event = function (event) {
      if (!(el.contains(event.target))) {
          vnode.context.$emit('emitedEvent')
      }
    }
    document.addEventListener('click', el.event)
  },
  unbind: function (el) {
    document.removeEventListener('click', el.event)
  },
})

Из v-on:emitedEvent я получаю:

Invalid handler for event "emitedEvent": got undefined
Answer 1

Давайте по-порядку. Насколько я понял, вы пытаетесь реализовать что-то наподобие всплывающих подсказок. А конкретно - их скрытие при клике где угодно, но не на них самих.

Подсказка у вас представлена компонентом <tooltip>, что уже является ошибкой №1. Название компонента должно обязательно содержать дефис и, следовательно, состоять как минимум из двух слов, этим дефисом разделенных. На время представим, что ваш компонент называется <my-tooltip> и пойдем дальше.

Property or method "clickEvent" is not defined on the instance but referenced during render.

Очевидно, что метод clickEvent не определен. Но исходя из того куска кода, что прикрепили Вы, видно обратное. А из всего этого следует, что определен-то он определен, да определен не там, где надо. В процессе написания этого ответа выяснилось (в комментариях к вопросу), что так оно и есть. Вместо того, чтобы clickEvent был методом того компонента, в котором <my-tooltip> используется, вы сделали его методом самого <my-tooltip>, что, собственно, и явилось ошибкой №2.

Ошибка №3 - архитектурная. Не столько ошибка даже, скорее просто излишество - код, который ошибку сам по себе не вызывает, но и смысловой нагрузки никакой не несет. Я говорю о вот этой части

<my-tooltip v-click-outside="clickEvent" ...>

Конкретно в Вашем случае нет абсолютно никакой необходимости передавать функцию обратного вызова директиве в качестве аргумента. Обычно эта возможность используется для какой-то настройки, передачи каких-то параметров. В вашем же случае можно обойтись без нее.

4-ой ошибкой и, я бы сказал, решающей, является то, что вы пытаетесь выбросить событие на root элементе приложения. Обращаясь к vNode.context вы обращаетесь не к самому компоненту <my-tooltip>, а к главному инстансу приложения (скорее всего, не факт на самом деле), который обычно имеет id app (или что-то в этом духе). Вместо этого событие должно выбрасываться на том компоненте, на котором оно ловится. Поэтому если у вас написано <my-tooltip @myEvent="hideMe">, то и $emit должен вызываться именно на инстансе компонента <my-tooltip>.

Логичным будет вопрос "а как?". Спасибо Эвану, он предусмотрел возможную необходимость поиска компонента и услужливо добавил объекту vNode свойство componentInstance. Исходя из вышесказанного выброс события должен выглядеть как vNode.componentInstance.$emit("myEvent").

Все ваши ошибки я исправил и создал для вас JSFiddle. Ниже то же самое (возможно даже когда-нибудь будет с комментариями), но в виде сниппета.

Vue.directive("click-outside", { 
  bind(el, binding, vNode) { 
    document.addEventListener("click", (event) => { 
      if (event.target !== el) { 
        vNode.componentInstance.$emit("clickedout"); 
      } 
    }); 
  } 
}); 
 
Vue.component("my-tooltip", { 
  template: "<div><slot></slot></div>" 
}); 
 
new Vue({ 
  el: "#app", 
  data: {}, 
  methods: { 
    hide() { 
      console.log("Hiding the toooltip..."); 
    } 
  } 
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> 
<div id="app"> 
  <my-tooltip id="tooltip" v-click-outside @clickedout="hide">Click outside!</my-tooltip> 
  Lorem ipsum dolor sit amet, consectetur adipisicing elit. Impedit dolor placeat, exercitationem earum voluptatem labore. Ducimus repellendus incidunt eum at, accusantium dolor, aperiam ea rerum amet inventore numquam vel illum. 
</div>

Важно: обратите внимание, что @clickedout здесь нечувствителен к регистру (как и все остальное в HTML, собственно). Поэтому, выбрасывая в своем коде myEvent вы на самом деле, согласно договоренностям Vue, должны ловить не @myEvent, а @my-event, если я правильно помню все их соглашения. Возможно, это даже явилось 5-ой ошибкой, но жирным выделять не буду, поскольку неуверен. Общее правило таково, что имя выбрасываемого события в идеале должно быть написано в одно слово и только маленькими буквами (типа как нативные mouseout, mousedown и т.п.).

И в конце хотелось бы добавить, что данная логика в целом будет является относительно жизнеспособной только при том условии, что сама подсказка у вас периодически на странице то появляется, то исчезает, но уж никак не находится на ней постоянно. Потому как в этом случае на документе будет постоянно висеть обработчик события, реагирующий на любой клик на этом документе, что уже само по себе является далеко не лучшей практикой.

READ ALSO
Почему не сменяется фон?

Почему не сменяется фон?

Всем привет! Сначала были проблемы с фоном, но после множества попыток, я смог зафиксировать фон, чтобы он не сменялся после обновления или...

149
Приложение TodoMVC

Приложение TodoMVC

Есть пример приложения TodoMVC с официального сайта VueJS

152
Значение термина pattern в React

Значение термина pattern в React

Помогите найти пожалуйста доступное определение термина pattern

104
Как ограничить вращение камеры TrackballControls в three.js

Как ограничить вращение камеры TrackballControls в three.js

Я использую вот такой код для ограничения вращения камеры

146