Vue.js 自定义指令:$refs 为空
Vue.js custom directive: $refs is empty
背景
我按照本指南创建了一个自定义指令(用于检测元素外的点击):https://tahazsh.com/detect-outside-click-in-vue。
元素:
<button
id='burger'
ref='burger'
class='burger button is-white'>
<span class='burger-top' aria-hidden='true' style='pointer-events: none;'></span>
<span class='burger-bottom' aria-hidden='true' style='pointer-events: none;'></span>
</button>
指令:
import Vue from 'vue';
let handleOutsideClick;
Vue.directive('outside', {
bind(el, binding, vnode) {
setTimeout(() => {
handleOutsideClick = (e) => {
e.stopPropagation();
const { handler, exclude } = binding.value;
let clickedOnExcludedEl = false;
exclude.forEach((refName) => {
if (!clickedOnExcludedEl) {
const excludedEl = vnode.context.$refs[refName]
console.log(vnode.context.$refs);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
vnode.context[handler]();
}
};
document.addEventListener('click', handleOutsideClick);
document.addEventListener('touchstart', handleOutsideClick);
}, 50);
},
unbind() {
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('touchstart', handleOutsideClick);
},
});
请注意,我不得不添加 setTimeout
。
在边栏组件中使用指令:
<div
id='sidebar'
class='sidebar-container'
v-show='toggleSideBar'
v-outside='{
handler: "close",
exclude: [
"burger",
],
}'>
<div class='sidebar-content'>
// other content
</div>
</div>
问题
$refs
是空的,即使元素显然有一个 ref
.
这一点在 console.log(vnode.context.$refs);
中也很清楚。
值得注意的是,通过 id 获取元素有效:
exclude.forEach((id) => {
if (!clickedOnExcludedEl) {
const excludedEl = document.getElementById(id);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
问题
为什么元素明明存在并且可以通过id访问,却$refs
为空?
是这样,因为你的ref
不是refName
。
你的 ref 是“burger”,但 refName 是 exclude
属性 给 v-closable
然后你重组。
在您链接的教程中,refName
是 button
。所以我假设它在您的代码中是相同的。
确保 ref="burger"
和
v-closable="{ exclude: ['burger'], // this is your refName handler: 'onClose' }"
相同。
目前编写此代码的方式 ref='burger'
和 v-outside
必须在同一模板中。
关键一行是:
const excludedEl = vnode.context.$refs[refName]
vnode.context
将引用侧边栏组件的 Vue 实例。 $refs
必须在该组件的模板中定义。
$refs
对于特定的 Vue 实例是本地的。 'Vue instance' 我的意思是组件实例,如果该术语更清楚的话。因此侧边栏将有一组 $refs
并且导航栏将有其自己的一组 $refs
.
根据问题下的评论,该按钮当前似乎已在导航栏组件的模板中定义。所以你最终会在导航栏的 $refs
中得到一个 burger
引用,但指令正在侧边栏的 $refs
.
中寻找它
该指令的编写方式需要一个类似这样的模板:
<div>
<div
v-outside='{
handler: "close",
exclude: ["burger"]
}'
>
...
</div>
<button ref='burger'>...</button>
</div>
此处 v-outside
和 ref='burger'
都在同一个模板中,因此一切正常。一旦您将按钮移至其他模板,它将无法找到它。
使用 id
和 document.getElementById
是一种非常不同的情况。元素 id 是全局注册的。元素在 DOM 中的位置无关紧要,只要它存在就会被找到。
背景
我按照本指南创建了一个自定义指令(用于检测元素外的点击):https://tahazsh.com/detect-outside-click-in-vue。
元素:
<button
id='burger'
ref='burger'
class='burger button is-white'>
<span class='burger-top' aria-hidden='true' style='pointer-events: none;'></span>
<span class='burger-bottom' aria-hidden='true' style='pointer-events: none;'></span>
</button>
指令:
import Vue from 'vue';
let handleOutsideClick;
Vue.directive('outside', {
bind(el, binding, vnode) {
setTimeout(() => {
handleOutsideClick = (e) => {
e.stopPropagation();
const { handler, exclude } = binding.value;
let clickedOnExcludedEl = false;
exclude.forEach((refName) => {
if (!clickedOnExcludedEl) {
const excludedEl = vnode.context.$refs[refName]
console.log(vnode.context.$refs);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
vnode.context[handler]();
}
};
document.addEventListener('click', handleOutsideClick);
document.addEventListener('touchstart', handleOutsideClick);
}, 50);
},
unbind() {
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('touchstart', handleOutsideClick);
},
});
请注意,我不得不添加 setTimeout
。
在边栏组件中使用指令:
<div
id='sidebar'
class='sidebar-container'
v-show='toggleSideBar'
v-outside='{
handler: "close",
exclude: [
"burger",
],
}'>
<div class='sidebar-content'>
// other content
</div>
</div>
问题
$refs
是空的,即使元素显然有一个 ref
.
这一点在 console.log(vnode.context.$refs);
中也很清楚。
值得注意的是,通过 id 获取元素有效:
exclude.forEach((id) => {
if (!clickedOnExcludedEl) {
const excludedEl = document.getElementById(id);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
问题
为什么元素明明存在并且可以通过id访问,却$refs
为空?
是这样,因为你的ref
不是refName
。
你的 ref 是“burger”,但 refName 是 exclude
属性 给 v-closable
然后你重组。
在您链接的教程中,refName
是 button
。所以我假设它在您的代码中是相同的。
确保 ref="burger"
和
v-closable="{ exclude: ['burger'], // this is your refName handler: 'onClose' }"
相同。
目前编写此代码的方式 ref='burger'
和 v-outside
必须在同一模板中。
关键一行是:
const excludedEl = vnode.context.$refs[refName]
vnode.context
将引用侧边栏组件的 Vue 实例。 $refs
必须在该组件的模板中定义。
$refs
对于特定的 Vue 实例是本地的。 'Vue instance' 我的意思是组件实例,如果该术语更清楚的话。因此侧边栏将有一组 $refs
并且导航栏将有其自己的一组 $refs
.
根据问题下的评论,该按钮当前似乎已在导航栏组件的模板中定义。所以你最终会在导航栏的 $refs
中得到一个 burger
引用,但指令正在侧边栏的 $refs
.
该指令的编写方式需要一个类似这样的模板:
<div>
<div
v-outside='{
handler: "close",
exclude: ["burger"]
}'
>
...
</div>
<button ref='burger'>...</button>
</div>
此处 v-outside
和 ref='burger'
都在同一个模板中,因此一切正常。一旦您将按钮移至其他模板,它将无法找到它。
使用 id
和 document.getElementById
是一种非常不同的情况。元素 id 是全局注册的。元素在 DOM 中的位置无关紧要,只要它存在就会被找到。