javascript - 单击下拉列表中的元素时关闭下拉列表

标签 javascript reactjs dropdown

我正在我的应用程序中开发通知功能(非常类似于 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>
    );
  }
}

ListNotification都是愚蠢的功能组件,所以我不会在这里发布它们的代码。 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) 的执行使用requestAnimationFramesetTimeout ,但也没有成功。

<小时/>

解决方案:

根据@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.pathnamecomponentDidUpdate 时是否已更改:

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/

相关文章:

flask - 破折号-Plotly : keep dropdown selection on page reload

javascript - 如何在MongoDB中存储和区分数据?

javascript - 类型错误 : Cannot read property 'book' of undefined at BookDetails. 渲染

javascript - Bootstrap 数据切换在浏览器中消失

html - Bootstrap 下拉菜单 a :hover not full li:hover

javascript - 在react-table-2的同一列中显示名字和姓氏

javascript - 我有 12 个项目的限制,但 dynamodb 查询总是只返回 10 个

javascript - Angular 中的条件 "otherwise"路由

javascript - Screeps:在 creeps 内存中存储对源的引用?

javascript - React 测试抛出警告 'Invalid DOM property ` %s`。您的意思是 `%s` 吗?%s'