Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[提案] useClickAway 的 target 传函数时,支持返回多个 dom target #1988

Open
Xekin97 opened this issue Nov 25, 2022 · 11 comments
Open
Assignees
Labels
feature New feature or request

Comments

@Xekin97
Copy link

Xekin97 commented Nov 25, 2022

涉及的hook

useClickAway()

提案内容

当前行为: 传入 useClickAway() 的 targets 为函数时,只能返回一个 dom target

修改为: 传入useClickAway()的 targets 为函数时,支持返回多个 dom target

背景

需求场景,在使用 Antd Menu 组件做右键菜单时,由于子级的 SubMenu 和主 Menu 不在同一个 wrapper dom 中,导致在点击 SubMenuItem 前,先触发了 clickAway 回调执行的 hide 方法。无法点击命中 SubMenuItem

问题在于

  • Menu 并没有暴露 SubMenu 的 ref
  • 页面中可能存在多个复用组件,使用方法条件作为 target 时,document.querySelector 只能访问到第一个组件的 dom

所以希望条件支持返回多个 dom,例如:

useClickAway(() => {
    doSomething()
}, () => document.querySelectorAll('.list-item'))

涉及的源码

useEffectWithTarget(
() => {
const handler = (event: any) => {
const targets = Array.isArray(target) ? target : [target];
if (
targets.some((item) => {
const targetElement = getTargetElement(item);
return !targetElement || targetElement.contains(event.target);
})
) {
return;
}
onClickAwayRef.current(event);
};

if (isFunction(target)) {
targetElement = target();
} else if ('current' in target) {

@miracles1919
Copy link
Collaborator

miracles1919 commented Nov 28, 2022

我觉得可以,来个 PR?

@miracles1919
Copy link
Collaborator

突然意识到,这样不就好了

const list = document.querySelectorAll('.list-item')
useClickAway(() => {
    doSomething()
}, Array.from(list))

@captain1023
Copy link
Contributor

突然意识到,这样不就好了

const list = document.querySelectorAll('.list-item')
useClickAway(() => {
    doSomething()
}, Array.from(list))

我觉得你说的对=.= 但我也不确定我是不是理解了Xekin97提案想表达的意思😂

@miracles1919
Copy link
Collaborator

那这个 issue 我先关了,有回复的话可以继续讨论 ~

@Xekin97
Copy link
Author

Xekin97 commented Nov 30, 2022

突然意识到,这样不就好了

const list = document.querySelectorAll('.list-item')
useClickAway(() => {
    doSomething()
}, Array.from(list))

这个是不是执行过程都不太一样了?

如果我没有 rerender 这个 hook 的外层组件,那 list 返回的结果就不会变化了

@miracles1919
Copy link
Collaborator

效果是一样的,你可以自己试一下

@Xekin97
Copy link
Author

Xekin97 commented Nov 30, 2022

我测试了一下,完全不行

codesandbox

@miracles1919
Copy link
Collaborator

你是对的

@miracles1919 miracles1919 reopened this Nov 30, 2022
@captain1023
Copy link
Contributor

我测试了一下,完全不行

codesandbox

https://codesandbox.io/s/zhi-chi-duo-ge-dom-dui-xiang-forked-fwirso?file=/App.tsx
或许可以试试这样,但React的Hook在第一次执行的时候会在dom真正渲染出来之前,所以query Selector获取不到对应的dom标签.除了ref可以好像没有什么其他方式可以获取到更新后的dom(眼界有限),使用useMutationObserver可以一定程度上满足需求,但第一次点击按钮的时候也仍会有问题.

前排蹲个答案😂

@Xekin97
Copy link
Author

Xekin97 commented Dec 4, 2022

看了大半天代码,这里头的逻辑牵涉的地方太多,居然有点无从下手 :(

@AnathanPham
Copy link

AnathanPham commented Feb 5, 2023

这个需求看起来是一个悖论。
除非舍弃事件代理的根节点ShadowRoot的场景。
如果支持传入函数计算动态target,那么事件代理的根节点变得不可靠。你无法预测动态target是否全部为ShadowRoot的子节点。

对于源码

useEffectWithTarget(
() => {
const handler = (event: any) => {
const targets = Array.isArray(target) ? target : [target];
if (
targets.some((item) => {
const targetElement = getTargetElement(item);
return !targetElement || targetElement.contains(event.target);
})
) {
return;
}
onClickAwayRef.current(event);
};
const documentOrShadow = getDocumentOrShadow(target);

如果去支持动态target,你不得不在 handler 中获取动态的target

+ const realTarget = isFunction(target) ? target() : target; 
const targets = Array.isArray(realTarget) ? realTarget : [realTarget];

但是事件代理的节点的判定却依赖了target

// 为此必须添加下面这一行,但是这样会让事件代理的根节点变得不可靠
const realTarget = isFunction(target) ? target() : target; 
const documentOrShadow = getDocumentOrShadow(target);

例如,第一次的render,动态计算得到的target为ShadowRoot的子节点。但是第二次计算得到的target不再是ShadowRoot的子节点,此时事件依然绑定在ShadowRoot上(因为useClickAway所在的组件可能没有更新,进而useEffectWithTarget回调不会执行)
。然后点击target外部的元素,handler不会被执行--这不符合预期。

不过,动态target可能会很有用,如果可以去掉ShadowRoot的判断的话:p

@liuyib liuyib added the feature New feature or request label Feb 23, 2023
@liuyib liuyib self-assigned this Feb 23, 2023
@liuyib liuyib added this to To do in ahooks tasks via automation Feb 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
No open projects
Development

No branches or pull requests

5 participants