簡體   English   中英

React - 如何檢測是否單擊了子組件

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

我在這里有一個測試站點

請做:

  • 訪問該站點並單擊右上角的漢堡包圖標。 側導航應該打開。
  • 再次執行相同的操作,您將看到我當前遇到的問題。

側面導航沒有正確關閉,因為我有兩個相互沖突的功能正在運行。

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

第二個沖突的 function由側導航面板組件使用。 它綁定到一個鈎子,該鈎子使用其ref來檢查用戶是否單擊了組件的內部或外部。

這些功能一直有效,直到用戶在側導航打開時單擊漢堡菜單。 漢堡包在技術上位於側導航組件之外,但覆蓋在其頂部,因此點擊不會注冊為外部。 這導致兩個函數一個接一個地觸發。 “點擊外部”鈎子觸發並將側導航上下文設置為假,關閉它。 漢堡切換 function 然后觸發,發現上下文設置為 false 並將其更改回 true。 因此立即關閉然后重新打開側導航。

我嘗試的解決方案如下所示。 我試圖在漢堡包子組件中分配一個ref並將其傳遞給側導航父級。 我想嘗試比較從useClickedOutside返回的回調與給父的toggleRef但漢堡組件,然后如果它們匹配則什么都不做。 我希望,這將使該交互中的一個功能失效。 不完全確定這是嘗試實現此類目標的最佳方式。

也許我可以得到側面導航邊界框,然后檢查點擊坐標是否落在其中?

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

我建議使用這個 package: https://github.com/airbnb/react-outside-click-handler 它處理您提到的一些邊緣情況,如果您對細節感興趣,可以查看它們的源代碼。

然后您需要保留有關側邊欄是否在 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>
  );
}

這樣你就不需要過多地處理 refs 和 handlers 了。

我回答這個問題的方法是避免完全檢測到側導航之外的點擊,而是檢測點擊是否在網站主體之外。

如果為真,那么我調用導航上下文並將側導航設置為假,就像我在側導航本身中嘗試的那樣。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM