我正在我的应用程序中开发通知功能(非常类似于 Facebook 通知)。
当我单击标题导航中的按钮时,下拉列表将打开并显示通知列表。该通知有 Link
(来自 react-router
)在其中。
我需要做的是每当 Link
时关闭下拉菜单被点击。
以下是我目前的大致层次结构:
Header > Navigation > Button > Dropdown > List > Notification > Link
由于下拉功能被多次使用,我已将其行为抽象为使用 render prop 的 HOC:
export default function withDropDown(ClickableElement) {
return class ClickableDropdown extends PureComponent {
static propTypes = {
children: PropTypes.func.isRequired,
showOnInit: PropTypes.bool,
};
static defaultProps = {
showOnInit: false,
};
state = {
show: !!this.props.showOnInit,
};
domRef = createRef();
componentDidMount() {
document.addEventListener('mousedown', this.handleGlobalClick);
}
toggle = show => {
this.setState({ show });
};
handleClick = () => this.toggle(true);
handleGlobalClick = event => {
if (this.domRef.current && !this.domRef.current.contains(event.target)) {
this.toggle(false);
}
};
render() {
const { children, ...props } = this.props;
return (
<Fragment>
<ClickableElement {...props} onClick={this.handleClick} />
{this.state.show && children(this.domRef)}
</Fragment>
);
}
};
}
上面的 HOC 包含 Button
组件,所以我有:
const ButtonWithDropdown = withDropdown(Button);
class NotificationsHeaderDropdown extends PureComponent {
static propTypes = {
data: PropTypes.arrayOf(notification),
load: PropTypes.func,
};
static defaultProps = {
data: [],
load: () => {},
};
componentDidMount() {
this.props.load();
}
renderDropdown = ref => (
<Dropdown ref={ref}>
{data.length > 0 && <List items={this.props.data} />}
{data.length === 0 && <EmptyList />}
</Dropdown>
);
render() {
return (
<ButtonWithDropdown count={this.props.data.length}>
{this.renderDropdown}
</ButtonWithDropdown>
);
}
}
List
和Notification
都是愚蠢的功能组件,所以我不会在这里发布它们的代码。 Dropdown
几乎是一样的,不同之处在于它使用 ref forwarding .
我真正需要的是调用 .toggle()
方法来自ClickableDropdown
由 HOC 创建,每当我单击 Link
时就会调用在列表中。
有什么办法可以做到这一点而不通过.toggle()
方法下来Button > Dropdown > List > Notification > Link
子树?
我正在使用redux
,但我不确定这是我会放在商店里的东西。
或者我应该使用 DOM API 强制处理这个问题,通过更改 handleGlobalClick
的实现来自ClickableDropdown
?
编辑:
我正在尝试使用命令式方法,因此我更改了 handleGlobalClick
方法:
const DISMISS_KEY = 'dropdown';
function contains(current, element) {
if (!current) {
return false;
}
return current.contains(element);
}
function isDismisser(dismissKey, current, element) {
if (!element || !contains(current, element)) {
return false;
}
const shouldDismiss = element.dataset.dismiss === dismissKey;
return shouldDismiss || isDismisser(dismissKey, current, element.parentNode);
}
// Then...
handleGlobalClick = event => {
const containsEventTarget = contains(this.domRef.current, event.target);
const shouldDismiss = isDismisser(
DISMISS_KEY,
this.domRef.current,
event.target
);
if (!containsEventTarget || shouldDismiss) {
this.toggle(false);
}
return true;
};
然后我改变了Link
包括 data-dismiss
属性:
<Link
to={url}
data-dismiss="dropdown"
>
...
</Link>
现在下拉菜单已关闭,但我没有重定向到提供的 url
不再了。
我试图推迟 this.toggle(false)
的执行使用requestAnimationFrame
和setTimeout
,但也没有成功。
解决方案:
根据@streletss的回答,我想出了以下解决方案:
为了尽可能通用,我创建了一个 shouldHideOnUpdate
ClickableDropdown
中的 Prop 下拉组件,其 Hindley-Milner 风格的签名是:
shouldHideOnUpdate :: Props curr, Props prev => (curr, prev) -> Boolean
这是 componentDidUpdate
实现:
componentDidUpdate(prevProps) {
if (this.props.shouldHideOnUpdate(this.props, prevProps)) {
this.toggle(false);
}
}
这样,我就不需要使用 withRouter
直接HOC在我的withDropdown
HOC。
因此,我解除了定义隐藏下拉菜单的条件给调用者的责任,我的情况是 Navigation
组件,我做了这样的事情:
const container = compose(withRouter, withDropdown);
const ButtonWithDropdown = container(Button);
function routeStateHasChanged(currentProps, prevProps) {
return currentProps.location.state !== prevProps.location.state;
}
// ... then
render() {
<ButtonWithDropdown shouldHideOnUpdate={routeStateHasChanged}>
{this.renderDropdown}
</ButtonWithDropdown>
}
最佳答案
看来您可以简单地使用 withRouter
HOC 并检查 this.props.location.pathname
在 componentDidUpdate
时是否已更改:
export default function withDropDown(ClickableElement) {
class ClickableDropdown extends Component {
// ...
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
this.toggle(false);
}
}
// ...
};
return withRouter(ClickableDropdown)
}
关于javascript - 单击下拉列表中的元素时关闭下拉列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53227157/