Команда элемента clickoutside анализ исходного кода

Vue.js

clickoutside — это настраиваемая команда, реализованная Element-ui. Как следует из названия, эта команда используется для обработки событий щелчка за пределами целевого узла. Она часто используется для обработки закрытия расширенного содержимого, такого как раскрывающиеся меню. Селектор выбора пользовательского интерфейса и раскрывающееся меню раскрывающегося списка Эта директива используется в таких компонентах, как всплывающее окно Popover и т. д., поэтому эта директива очень полезна при реализации некоторых пользовательских компонентов.

Чтобы проанализировать исходный код, мы должны сначала понять Vue.пользовательская директива. Пользовательские директивы определяются следующим образом:

// 注册一个全局自定义指令 
Vue.directive('directiveName', {
  bind: function(el, binding, vnode){
    // 当指令第一次绑定到元素时调用,常用来进行一些初始化设置
  	...
  },
  inserted: function(el, binding, vnode){
    // 当被绑定的元素插入到 DOM 中时……
  	...
  },
  update: function(el, binding, vnode, oldVnode){
    // 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前
  	...
  },
  componentUpdated: function(el, binding, vnode, oldVnode){
    // 指令所在组件的 VNode 及其子 VNode 全部更新后调用
  	...
  },
  unbind: function(el, binding, vnode){
    // 只调用一次,指令与元素解绑时调用,类似于beforeDestroy的功能
  	...
  }
});

Видно, что в конфигурационном объекте всего 5 необязательных функций-хуков, а их параметры имеют 4 параметра, а именноel、binding、vnode、oldVnode

  • el : элемент, к которому привязана директива, который можно использовать для прямого управления DOM.
  • привязка : объект, который содержит пользовательскую подробную информацию. Он внутренне собирает данные, такие как значения, модификаторы, параметры и другие данные, передаваемые при использовании пользовательских инструкций. Подробную информацию можно увидеть в официальной документации, которая очень подробно описана.
  • vnode : виртуальный узел, сгенерированный компиляцией Vue.
  • oldVnode: до этого обновления Vnode виртуальный узел, сгенерированный в последний раз,update а также componentUpdatedВ наличии крючки.

Ознакомившись с содержанием пользовательской инструкции, давайте проанализируем конкретную реализацию clickoutside.

import Vue from 'vue';
import { on } from 'element-ui/src/utils/dom';

const nodeList = [];
const ctx = '@@clickoutsideContext';

let startClick;
let seed = 0;

!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e));

!Vue.prototype.$isServer && on(document, 'mouseup', e => {
  nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
});

function createDocumentHandler(el, binding, vnode) {
  return function(mouseup = {}, mousedown = {}) {
    ...
  };
}

let startClick;
let seed = 0;

export default {
  bind(el, binding, vnode) {
    ...
  },

  update(el, binding, vnode) {
    ...
  },

  unbind(el) {
    ...
  }
};

Выше приведен упрощенный исходный код.Вы можете видеть, что сначала вводятся Vue и инструментальная функция для привязки событий, а затем определяются две глобальные константы.nodeListа такжеctx. список узлов представляет собойсборщик элементов, будет хранить все элементы dom на странице, связанные с инструкцией clickoutside, а ctx определяет пространство имен (должно быть специальным, чтобы предотвратить то же имя, что и у других функций),Он будет добавлен позже, так как свойства элемента el, который будет проанализирован позже.

Затем используйте ранее представленный Vue для суждения, а несерверная сторона добавляет к объекту документаmousedownа такжеmouseupсобытие, вmousedownВ обратном вызове события сохраните объект события вstartClickв глобальных переменных, вmouseupОбход в обратном вызове событияnodeList,ПотомВыполнить каждый узел (то есть элемент el, привязанный ранее сохраненной инструкцией clickoutside), хранящийся в атрибуте ctx.documentHandlerфункция. Значение свойства ctx будет представлено позже.

Наконец, экспортируйтеclickoutsideОбъекты конфигурации в использованииclickoutsideИмпортируйте объект конфигурации в компонент директивы, а затем зарегистрируйте его локально в компоненте, чтобы использовать его.

используется в объекте конфигурацииbind、update、unbindДля определения инструкции clickoutside используются три функции-ловушки.Главное — собрать соответствующую информацию пользовательской инструкции и сохранить ее в функции ctx el. Давайте подробнее рассмотрим этот процесс сбора.

Первая — это функция хука привязки:

bind(el, binding, vnode) {
  nodeList.push(el);
  const id = seed++;
  el[ctx] = {
    id,
    documentHandler: createDocumentHandler(el, binding, vnode),
    methodName: binding.expression,
    bindingFn: binding.value
  };
}

Здесь сначала вставьте el непосредственно в nodeList, чтобы каждый раз, когда инструкция clickoutside привязывалась к странице, связанный элемент сохранялся в nodeList, то есть, как упоминалось ранее.сборщик элементов. Далее идет глобальная переменная seed++, и присваивается временная переменная id, последней является ctx характеристика el присваивания, а ее значением является объект, в том числе внутренний:

  • id : глобальный уникальный идентификатор, сгенерированный ранее для идентификации директивы clickoutside.
  • documentHandler : функция обратного вызова, созданная createDocumentHandler. Как упоминалось в предыдущем анализе, в обратном вызове события mouseup, связанного со страницей, nodeList будет пройден, и функция documentHandler для функции ctx каждого элемента привязки el будет выполняться отдельно.Эта функция генерируется здесь, Что касается того, что делает эта callback-функция, мы подробно разберем ее позже.
  • methodName : binding.expression, просмотр пользовательской инструкцииДокументацияЗнать,binding.expressionЗначение представляет собой выражение инструкции в строковой форме. Например, есть<div v-my-directive="1 + 1"></div>,ноbinding.expressionЗначение1 + 1
  • bindingFn : binding.value, значение привязки инструкции или приведенный выше пример, затемbinding.valueЗначение равно 2 (1 + 1 равно 2), т.е.Когда значением инструкции является выражение js,**binding.expresssion**само выражение, строка и**binding.value**является значением выражения.

Далее мы смотрим на хук обновления:

update(el, binding, vnode) {
	el[ctx].documentHandler = createDocumentHandler(el, binding, vnode);
	el[ctx].methodName = binding.expression;
	el[ctx].bindingFn = binding.value;
}

Вы можете видеть, что содержание хука обновления очень простое, то есть, когда компонент обновляется, значение в атрибуте ctx элемента привязки el обновляется.

Затем давайте посмотрим на последний хук unbind :

unbind(el) {
  let len = nodeList.length;

  for (let i = 0; i < len; i++) {
    if (nodeList[i][ctx].id === el[ctx].id) {
      nodeList.splice(i, 1);
      break;
    }
  }
  delete el[ctx];
}

Этот хук тоже очень простой, когдаclickoutsideКогда инструкция не привязана к элементу el, проходимnodeList, найденный по идентификатору в функции ctxnodeListТекущий несвязанный элемент el, хранящийся в el, удаляет его из nodeList и удаляет атрибут ctx для el.

Выше приведены все операции, выполняемые в объекте конфигурации директивы clickoutside, резюмированные следующим образом:

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

Как упоминалось ранее, в обратном вызове события mouseup, связанного со страницей, nodeList будет пройден, и функция ctx для каждого собранного элемента привязки el будет выполняться отдельно.documentHandlerфункция. И функция передается черезcreateDocumentHandlerфункция сгенерирована, давайте посмотрим, что делает эта функция.

function createDocumentHandler(el, binding, vnode) {
  return function(mouseup = {}, mousedown = {}) {
    if (!vnode ||
      !vnode.context ||
      !mouseup.target ||
      !mousedown.target ||
      el.contains(mouseup.target) ||
      el.contains(mousedown.target) ||
      el === mouseup.target ||
      (vnode.context.popperElm &&
      (vnode.context.popperElm.contains(mouseup.target) ||
      vnode.context.popperElm.contains(mousedown.target)))) return;

    if (binding.expression &&
      el[ctx].methodName &&
      vnode.context[el[ctx].methodName]) {
      vnode.context[el[ctx].methodName]();
    } else {
      el[ctx].bindingFn && el[ctx].bindingFn();
    }
  };
}

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

  • vnode.contextСуществовать или не выходить
  • mouseup.targetСуществовать или не выходить
  • mousedown.targetСуществовать или не выходить
  • Содержит ли объект привязки elmouseup.target/mousedown.targetДочерний узел, если он содержит описание того, что щелчок находится внутри связанного элемента, он не будет выполненclickoutsideСодержание инструкции
  • Является ли объект привязки el равнымmouseup.target, что означает, что щелчок предназначен для привязки самого элемента, и он не выполняетсяclickoutsideСодержание инструкции
  • наконецvnode.context.popperElmЭта часть содержимого: Определите, находится ли щелчок в раскрывающемся меню.Если да, то он не щелкается за пределами элемента привязки, и содержимое инструкции clickoutside не выполняется.



微信截图_20190212155657.png

Как показано на рисунке, если щелчок находится в красной области, все не сработает.clickoutsideЛогика инструкции.

Если все вышеперечисленные условия соблюдены, оценивается значение, кэшированное замыканием, еслиmethodNameВыполнить этот метод, если он существует, выполнить его, если он не существуетbindingFn. Например:

<template>
	<div v-clickoutside="handleClose"></div>
</template>

<script>
  export default {
    data(){
      return {
        visible: false
      };
    },

    methods: {
      handleClose(){
        this.visible = false;
      }
    }
  }
</script>

В этом примереmethodNameилиbindingFnпередается через командуhandleCloseметод. Выполните этот метод, вы можете выполнитьclickoutsideлогика команды

ВышеупомянутоеdocumentHandlerГенерация методов и внутренняя логика. Благодаря этому методу и предыдущему анализу мы можем узнать, что когда страница связанаmouseupКогда событие срабатывает, оно проходитnodeList, выполнить функцию ctx каждого элемента привязки el по очередиdocumentHandlerметод. К выражению, переданному в инструкции, можно получить доступ внутри этого метода, и выражение будет выполнено после серии суждений, чтобы достичь цели выполнения данной логики вне целевого элемента щелчка, и эта заданная логика выполняется через пользовательская инструкция Значение , переданное в атрибут ctx элемента привязки el.

слишком далекоclickoutsideИсходный код проанализирован, вы можете увидетьclickoutsideИсходный код инструкции не сложен, но в нем все еще задействовано много контента.Есть много вещей, которые стоит изучить, например, использование характеристик элемента dom для хранения дополнительной информации, использование замыкания для кэширования переменных, как оценить, находится ли щелчок за пределами целевого элемента и Vue.Использование пользовательских директив и т. д.