Vue.js 自定义指令:$refs 为空

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\n
import 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

在侧边栏组件中使用该指令:

\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.

\n\n

这一点从 也可以清楚地看出console.log(vnode.context.$refs);

\n\n

值得注意的是,通过 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访问却是空的?

\n

ski*_*tle 4

当前编写此代码的方式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-outsideref='burger'都在同一个模板中,所以一切都会正常工作。一旦您将按钮移动到不同的模板,它就无法找到它。

使用idanddocument.getElementById是一个非常不同的场景。元素 id 是全局注册的。元素在 DOM 中的位置并不重要,只要它存在,就会被找到。