首页 > 解决方案 > React - 如何检测是否单击了子组件

问题描述

我在这里有一个测试站点

请做:

侧面导航没有正确关闭,因为我有两个相互冲突的功能正在运行。

一个冲突函数onClick实际汉堡包组件中的切换函数,它切换关联的上下文状态。

第二个冲突功能由侧导航面板组件使用。它绑定到一个钩子,该钩子使用它ref来检查用户是否单击了组件的内部或外部。

这些功能一直有效,直到用户在侧导航打开时单击汉堡菜单。汉堡包在技术上位于侧导航组件之外,但覆盖在其顶部,因此点击不会注册为外部。这导致两个函数一个接一个地触发。“点击外部”钩子触发并将侧导航上下文设置为假,关闭它。然后触发汉堡切换功能,发现上下文设置为 false 并将其更改回 true。因此立即关闭然后重新打开侧导航。

我尝试的解决方案如下所示。我试图ref在汉堡包子组件中分配一个并将其传递给侧导航父级。我想尝试比较从useClickedOutsidetoggleRef定到父级但汉堡包组件返回的回调,然后如果它们匹配则什么也不做。我希望,这将使该交互中的一个功能失效。不完全确定这是尝试实现此类目标的最佳方式。

也许我可以得到侧面导航边界框,然后检查点击坐标是否落在其中?

//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>
    </>
  );
}



上面使用的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]);
}

编辑

通过将切换组件移动到侧面导航组件内,设法解决了部分问题。它现在有自己的问题,因为当侧面导航道具更改时,汉堡包组件会完全重新渲染。试图弄清楚如何在这样的钩子场景中停止汉堡包重新渲染。我一直在阅读这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>
    </>
  );
}

标签: javascriptreactjsreact-nativenext.js

解决方案


我建议使用这个包:https ://github.com/airbnb/react-outside-click-handler 。它处理您提到的一些边缘情况,如果您对细节感兴趣,可以查看它们的源代码。

然后您需要保留有关侧边栏是否在状态下打开的信息并将其传递给受影响的组件,有点像这样(这样您还可以根据工具栏状态更改站点在其他地方的外观,例如禁用滚动条等):

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>
  );
}

这样你就不需要过多地处理 refs 和 handlers 了。


推荐阅读