简体   繁体   English

当组件在 React 功能组件中完全呈现时滚动到锚点

[英]Scroll to anchor when Component is fully rendered in React functional component

This a (very) simplified version of my component:这是我的组件的(非常)简化版本:

export const DynamicComponent: FC<DynamicComponentProps> = (props) => {
  const ref = useRef<HTMLElement>(null);
  const [isSticked, setIsSticked] = useState(false);
  const parentSticked = useContext(StickyContext);
  const [overridedStyles, setOverridedStyles] = useState(props.styles ?? {});
  const [overridedArgs, setOverridedArgs] = useState(props.args ?? {});
  const { config } = useContext(GlobalContext);
  const data = useContext(DataContext);
  const [state, setState] = useContext(StateContext);

  const mountComponent = useMemo(() => {
    if (typeof props.mount === "undefined") return true;
    if (typeof props.mount === "boolean") return props.mount;
    if (typeof props.mount === "number") return props.mount === 1;
    if (typeof props.mount === "string") {
      let mount = stateParser.parse(props.mount, state) as unknown;
      return mount == true;
    }
    return false;
  }, [state, props.mount]);



  useLayoutEffect(() => {
    setTimeout(() => {
      const anchorHash = location.hash;
      if (
        anchorHash &&
        document &&
        document.querySelector(anchorHash) &&
        !document
          .querySelector(anchorHash)
          ?.classList.contains("already-scrolled")
      ) {
        document?.querySelector(anchorHash)?.scrollIntoView();
        document?.querySelector(anchorHash)?.classList.add("already-scrolled");
      }
    }, 50);
  }, []);

  let output = mountComponent ? (
    <StickyContext.Provider value={{ sticked: isSticked }}>
      <StyledDynamicComponent
        {...props}
        ref={ref}
        isSticked={applyStickedStyles}
        args={overridedArgs}
        styles={overridedStyles}
      />
    </StickyContext.Provider>
  ) : null;

  return output;
};

The code inside the useLayoutEffect won't run correctly without the setTimeout because the component is not fully rendered and document?.querySelector(anchorHash) does not exist yet..如果没有 setTimeout,useLayoutEffect 中的代码将无法正确运行,因为组件尚未完全呈现并且document?.querySelector(anchorHash)尚不存在。

Tried with a window.onload but the code inside it will never run..尝试使用window.onload但其中的代码永远不会运行..

Is there a way to prevent using that horrendous setTimeout?有没有办法防止使用那个可怕的 setTimeout?

Also please note that the anchor or the anchored element are optional so I don't know how to use callaback refs另请注意,锚点或锚定元素是可选的,所以我不知道如何使用回调引用

Don't use document.querySelector and don't check class names, if you can use states for it.如果可以使用状态,请不要使用document.querySelector并且不要检查 class 名称。

You don't need setTimeout at all, as useEffect and useEffectLayout are more or less the same as componentDidMount :您根本不需要setTimeout ,因为useEffectuseEffectLayout或多或少与componentDidMount相同:

If you're migrating code from a class component, note useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate.如果您从 class 组件迁移代码,请注意 useLayoutEffect 在与 componentDidMount 和 componentDidUpdate 相同的阶段触发。 However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.但是,我们建议首先从 useEffect 开始,并且仅在导致问题时才尝试使用 useLayoutEffect。 useLayoutEffect-Docs useLayoutEffect-Docs

I tried to reduce your samle a little bit more and made it debuggable in the codesandbox (hopefully keeping your logic in tact).我试图进一步减少你的样本,并使其在代码盒中可调试(希望保持你的逻辑完整)。

编辑 React PlayGround(分叉)

But the most important part would be the following:但最重要的部分如下:

const ref = useRef();

useEffect(() => {
    if (!ref.current || !document) {
      return;
    }

    // check if a hash is provided
    // possible todo: is the current element id the same as the provided location hash id
    if(!location.hash) {
      return true;
    }

    // check if we've scrolled already
    if(scrolled) {
      return;
    }

    ref.current.scrollIntoView();

    console.log("scroll to view", ref);
    setScrolled(true);
}, [ref, location, scrolled]);


Your component will then be rendered each time, the ref , location , or scrolled vars have changed, but it should only scroll into view, if it hasn't done that before.然后,您的组件将在每次reflocationscrolled变量发生更改时被渲染,但它应该只滚动到视图中,如果它之前没有这样做的话。

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

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