简体   繁体   English

React - 如何检测是否单击了子组件

[英]React - How do I detect if a child component is clicked

I have a test site HERE我在这里有一个测试站点

Please do:请做:

  • Visit the site and click on the hamburger icon in the top right.访问该站点并单击右上角的汉堡包图标。 The side nav should open.侧导航应该打开。
  • Perform the same action again and you will see the problem I am currently having.再次执行相同的操作,您将看到我当前遇到的问题。

The side nav does not close properly because I have two conflicting functions operating.侧面导航没有正确关闭,因为我有两个相互冲突的功能正在运行。

The first conflicting function is an onClick toggle function within the actual hamburger component which toggles an associated context state. The first conflicting function is an onClick toggle function within the actual hamburger component which toggles an associated context state.

The second conflicting function is used by the side nav panel component.第二个冲突的 function由侧导航面板组件使用。 It is bound to a hook which uses its ref to check whether the user has clicked inside or outside of the component.它绑定到一个钩子,该钩子使用其ref来检查用户是否单击了组件的内部或外部。

These functions work up until the user clicks on the hamburger menu whilst the side nav is open.这些功能一直有效,直到用户在侧导航打开时单击汉堡菜单。 The hamburger is technically outside of the side nav component but overlayed on top of it, so the click does not register as outside.汉堡包在技术上位于侧导航组件之外,但覆盖在其顶部,因此点击不会注册为外部。 This results in both functions firing one after the other.这导致两个函数一个接一个地触发。 The "click outside" hook fires and sets the side nav context to false, closing it. “点击外部”钩子触发并将侧导航上下文设置为假,关闭它。 The hamburger toggle function then fires, finds the context set to false and changes it back to true.汉堡切换 function 然后触发,发现上下文设置为 false 并将其更改回 true。 Thus instantly closing and then reopening the side nav.因此立即关闭然后重新打开侧导航。

The solution I have attempted looks like the below.我尝试的解决方案如下所示。 I tried to assign a ref within the hamburger child component and pass it to the side nav parent.我试图在汉堡包子组件中分配一个ref并将其传递给侧导航父级。 I wanted to try and compare the callback returned from useClickedOutside to the toggleRef given to the parent but the hamburger component and then if they match then do nothing.我想尝试比较从useClickedOutside返回的回调与给父的toggleRef但汉堡组件,然后如果它们匹配则什么都不做。 This, I was hoping, would knock one of the functions out of action for that interaction.我希望,这将使该交互中的一个功能失效。 Not entirely sure this is the best way to attempt to achieve something like this.不完全确定这是尝试实现此类目标的最佳方式。

Perhaps I could get the side nav bounding box and then check if the click coordinates land within it?也许我可以得到侧面导航边界框,然后检查点击坐标是否落在其中?

//SideNav.jsx

const SideToggle = ({ sideNavToggle, sideIsOpen, setToggleRef }) => {
  useEffect(() => {
    const toggleRef = React.createRef();
    setToggleRef(toggleRef);
  }, []);

  return (
    <Toggle onClick={sideNavToggle}>
      <Bars>
        <Bar sideIsOpen={sideIsOpen} />
        <Bar sideIsOpen={sideIsOpen} />
        <Bar sideIsOpen={sideIsOpen} />
      </Bars>
    </Toggle>
  );
};

export default function SideNav() {
  const [toggleRef, setToggleRef] = useState(null);

  const { sideIsOpen, sideNavToggle, setSideIsOpen } = useContext(
    NavigationContext
  );
  const handlers = useSwipeable({
    onSwipedRight: () => {
      sideNavToggle();
    },
    preventDefaultTouchmoveEvent: true,
    trackMouse: true,
  });
  const sideRef = React.createRef();

  useClickedOutside(sideRef, (callback) => {
    if (callback) {
      if (callback === toggleRef) {
        return null;
      } else setSideIsOpen(false);
    }
  });

  return (
    <>
      <SideToggle
        sideIsOpen={sideIsOpen}
        sideNavToggle={sideNavToggle}
        setToggleRef={setToggleRef}
      />
      <div ref={sideRef}>
        <Side sideIsOpen={sideIsOpen} {...handlers}>
          <Section>
            <Container>
              <Col xs={12}>{/* <Menu /> */}</Col>
            </Container>
          </Section>
        </Side>
      </div>
    </>
  );
}



The useClickedOutside hook used above.上面使用的useClickedOutside钩子。

//use-clicked-outside.js

export default function useClickedOutside(ref, callback) {
  useEffect(() => {
    function handleClickOutside(event) {
      if (ref.current && !ref.current.contains(event.target)) {
        callback(event.target);
      } else return null;
    }

    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref]);
}

EDIT编辑

Managed to fix part of the problem by moving the toggle component inside of the side nav component.通过将切换组件移动到侧面导航组件内,设法解决了部分问题。 It now has its own problem in that the hamburger component completely rerenders when the side nav props change.它现在有自己的问题,因为当侧面导航道具发生变化时,汉堡包组件会完全重新渲染。 Trying to figure out how to stop the hamburger rerendering in a hooks scenario such as this.试图弄清楚如何在这样的钩子场景中停止汉堡包重新渲染。 I've been reading that React.memo may be the answer but unsure how to implement it as of yet.我一直在阅读React.memo可能是答案,但目前还不确定如何实现它。

//SideNav.jsx

export default function SideNav() {
  const { sideIsOpen, sideNavClose, sideNavOpen } = useContext(
    NavigationContext
  );

  const handlers = useSwipeable({
    onSwipedRight: () => {
      sideNavClose();
    },
    preventDefaultTouchmoveEvent: true,
    trackMouse: true,
  });

  const sideRef = React.createRef();

  useClickedOutside(sideRef, (callback) => {
    if (callback) {
      sideNavClose();
    }
  });

  const SideToggle = () => {
    const handleClick = () => {
      if (!sideIsOpen) {
        sideNavOpen();
      }
    };

    return (
      <Toggle onClick={() => handleClick()}>
        <Bars>
          <Bar sideIsOpen={sideIsOpen} />
          <Bar sideIsOpen={sideIsOpen} />
          <Bar sideIsOpen={sideIsOpen} />
        </Bars>
      </Toggle>
    );
  };

  return (
    <>
      <SideToggle />
      <div ref={sideRef}>
        <Side sideIsOpen={sideIsOpen} {...handlers}>
          <Section>
            <Container>
              <Col xs={12}>{/* <Menu /> */}</Col>
            </Container>
          </Section>
        </Side>
      </div>
    </>
  );
}

I'd recommend using this package: https://github.com/airbnb/react-outside-click-handler .我建议使用这个 package: https://github.com/airbnb/react-outside-click-handler It handles some edge cases you mention, you can check their source code if you are interested in the details.它处理您提到的一些边缘情况,如果您对细节感兴趣,可以查看它们的源代码。

Then you need to keep the information about whether the sidebar is opened in the state and pass it down to the affected components somewhat like this (this way you can also change how the site looks in other places depending on the toolbar state, eg. disable scrollbar etc):然后您需要保留有关侧边栏是否在 state 中打开的信息并将其传递给受影响的组件,有点像这样(这样您还可以根据工具栏 state 更改站点在其他地方的外观,例如禁用滚动条等):

function Site() {
  const [isOpened, setToolbarState] = React.useState(false);

  return (
    <React.Fragment>
      <Toolbar isOpened={isOpened} setToolbarState={setToolbarState} />
      {isOpened ? (<div>draw some background if needed</div>) : null}
      <RestOfTheSite />
    </React.Fragment>
  );
}

function Toolbar(props) {
  const setIsClosed = React.useCallback(function () {
    props.setToolbarState(false);
  }, [props.setToolbarState]);

  const setIsOpened = React.useCallback(function () {
    props.setToolbarState(true);
  }, [props.setToolbarState]);

  return ( 
    <OutsideClickHandler onOutsideClick={setIsClosed}>
      <div className="toolbar">
        <button onClick={setIsOpened}>open</button>
        ...
        {props.isOpened ? (<div>render links, etc here</div>) : null}
      </div>
    </OutsideClickHandler>
  );
}

This way you won't necessary need to juggle refs and handlers around too much.这样你就不需要过多地处理 refs 和 handlers 了。

The way I answered this was to avoid detecting a click outside of the side nav altogether and instead detected if the click was outside of the main body of the site.我回答这个问题的方法是避免完全检测到侧导航之外的点击,而是检测点击是否在网站主体之外。

If true, then I call the navigation context and set side nav to false the same as I was trying in the side nav itself.如果为真,那么我调用导航上下文并将侧导航设置为假,就像我在侧导航本身中尝试的那样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM