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