ant*_*tin 7 vue.js vuejs2 vue-directives
背景
\n\n我按照本指南创建了一个自定义指令(以检测元素外部的点击):https: //tahazsh.com/detect-outside-click-in-vue。
\n\n元素:
\n\n<button\n id=\'burger\'\n ref=\'burger\'\n class=\'burger button is-white\'>\n <span class=\'burger-top\' aria-hidden=\'true\' style=\'pointer-events: none;\'></span>\n\n <span class=\'burger-bottom\' aria-hidden=\'true\' style=\'pointer-events: none;\'></span>\n</button>\n
Run Code Online (Sandbox Code Playgroud)\n\n指示:
\n\nimport Vue from \'vue\';\n\nlet handleOutsideClick;\n\nVue.directive(\'outside\', {\n bind(el, binding, vnode) {\n setTimeout(() => {\n handleOutsideClick = (e) => {\n e.stopPropagation();\n\n const { handler, exclude } = binding.value;\n\n let clickedOnExcludedEl = false;\n\n exclude.forEach((refName) => {\n if (!clickedOnExcludedEl) {\n const excludedEl = vnode.context.$refs[refName]\n console.log(vnode.context.$refs);\n clickedOnExcludedEl = excludedEl.contains(e.target);\n }\n });\n\n if (!el.contains(e.target) && !clickedOnExcludedEl) {\n vnode.context[handler]();\n }\n };\n document.addEventListener(\'click\', handleOutsideClick);\n document.addEventListener(\'touchstart\', handleOutsideClick);\n }, 50);\n },\n unbind() {\n document.removeEventListener(\'click\', handleOutsideClick);\n document.removeEventListener(\'touchstart\', handleOutsideClick);\n },\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n请注意,我必须添加setTimeout
.
在侧边栏组件中使用该指令:
\n\n<div\n id=\'sidebar\'\n\xc2\xa0 class=\'sidebar-container\'\n\xc2\xa0 v-show=\'toggleSideBar\'\n\xc2\xa0 v-outside=\'{\n\xc2\xa0 \xc2\xa0 handler: "close",\n\xc2\xa0 \xc2\xa0 exclude: [\n\xc2\xa0 \xc2\xa0 \xc2\xa0 "burger",\n\xc2\xa0 \xc2\xa0 ],\n\xc2\xa0 }\'>\n\xc2\xa0 <div class=\'sidebar-content\'>\n\xc2\xa0 \xc2\xa0 // other content\n\xc2\xa0 </div>\n</div>\n
Run Code Online (Sandbox Code Playgroud)\n\n问题
\n\n$refs
为空,即使该元素明显具有ref
.
这一点从 也可以清楚地看出console.log(vnode.context.$refs);
。
值得注意的是,通过 id 获取元素是有效的:
\n\n exclude.forEach((id) => {\n if (!clickedOnExcludedEl) {\n const excludedEl = document.getElementById(id);\n clickedOnExcludedEl = excludedEl.contains(e.target);\n }\n });\n
Run Code Online (Sandbox Code Playgroud)\n\n问题
\n\n为什么$refs
元素明明存在并且可以通过id访问却是空的?
当前编写此代码的方式ref='burger'
必须v-outside
位于同一模板中。
关键行是:
const excludedEl = vnode.context.$refs[refName]
Run Code Online (Sandbox Code Playgroud)
vnode.context
将引用侧边栏组件的 Vue 实例。必须$refs
在该组件的模板中定义。
$refs
是特定 Vue 实例的本地变量。如果这个术语更清楚的话,我所说的“Vue 实例”是指组件实例。因此,侧边栏将有一组$refs
,导航栏将有自己的一组$refs
。
根据问题下的评论,该按钮当前似乎是在导航栏组件的模板中定义的。因此,您最终会burger
在导航栏中找到一个引用$refs
,但该指令正在侧边栏的 中查找它$refs
。
该指令的编写方式需要一个类似于以下的模板:
<div>
<div
v-outside='{
handler: "close",
exclude: ["burger"]
}'
>
...
</div>
<button ref='burger'>...</button>
</div>
Run Code Online (Sandbox Code Playgroud)
这里v-outside
和ref='burger'
都在同一个模板中,所以一切都会正常工作。一旦您将按钮移动到不同的模板,它就无法找到它。
使用id
anddocument.getElementById
是一个非常不同的场景。元素 id 是全局注册的。元素在 DOM 中的位置并不重要,只要它存在,就会被找到。