v-click-outside
介绍
使用在Vue
中的插件,一个附加的指令插件,作用是:
- 点击指定元素外的区域可以触发事件
- 可以绑定多个元素,一个元素上可以绑定多个事件
- 有中间件的概念,其实也就是个拦截器
核心实现代码
function onEvent({ el, event, handler, middleware }) {
// 判断点击事件触发的元素(event.target)是否是绑定元素(el)的子元素
const isClickOutside = event.target !== el && !el.contains(event.target)
// 如果不是外部则不触发
if (!isClickOutside) {
return
}
// middleware是中间件(拦截器),根据返回值可以决定是否拦截,同时还会执行middleware内部的代码
if (middleware(event, el)) {
// 绑定的点击元素外区域的触发函数
handler(event, el)
}
}
创建触发事件的实例
对其中的参数和格式做处理,为简化后续创建,处理后是一个对象,里面包含el
当前绑定元素和eventHandlers
绑定事件的数组列表
而eventHandlers
中每个对象又包含event
事件名和handler
事件处理函数
-
el
:绑定事件的元素 -
events
:数组,可以绑定多个事件,比如:blur,foucs等,默认是click(PC),移动端是click和tap -
handler
:绑定的事件触发的函数 -
middleware
:中间件
function createInstance({ el, events, handler, middleware }) {
return {
el,
eventHandlers: events.map((eventName) => ({
event: eventName,
handler: (event) => onEvent({ event, el, handler, middleware }),
})),
}
}
初始化实例并绑定
这里面有2个小细节:
- 采用定时器注册事件,实现类似异步注册,提高效率
- 将创建好的实例存放在数组缓存为后续注销和修改使用
function bind(el, { value }) {
// 处理参数和初始值问题
const { events, handler, middleware, isActive } = processDirectiveArguments(value)
// 是否可用的开关
if (!isActive) {
return
}
// 初始化实例
const instance = createInstance({ el, events, handler, middleware })
// 根据初始化后的实例,去循环绑定指定的事件和事件处理函数
instance.eventHandlers.forEach(({ event, handler }) =>
setTimeout(() => document.addEventListener(event, handler), 0),
)
// 存放在数组缓存中
directive.instances.push(instance)
}
注销已初始化的实例
有两点注意:
- 注销事件时,不能用
setTimeout
,因为需要同步全部注销完后做清除缓存实例的动作 -
removeEventListener
中的处理函数必须用addEventListener
中的处理函数,所以这里缓存就起到作用了,如果只是单纯的函数一样是没办法注销的
function removeInstance(el) {
// 读取缓存中el的实例
const instanceIndex = directive.instances.findIndex((instance) => instance.el === el)
// 如果不存在实例则停止注销
if (instanceIndex === -1) {
// Note: This can happen when active status changes from false to false
return
}
// 缓存中el的实例
const instance = directive.instances[instanceIndex]
// 循环注销
instance.eventHandlers.forEach(({ event, handler }) =>
document.removeEventListener(event, handler),
)
// 清除缓存中的注销实例
directive.instances.splice(instanceIndex, 1)
}
解读源码的原因
由于v-click-outside
只能在Vue
中以指令的,但是有时候可能用不了指令,比如动态生成的元素,也有可能不是在Vue
中想要用到,所以通过读源码开发一个原生js库
最后发布了: