当组件在 React 功能组件中完全呈现时滚动到锚点
Scroll to anchor when Component is fully rendered in React functional 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;
};
如果没有 setTimeout,useLayoutEffect 中的代码将不会 运行 正确,因为组件未完全呈现并且 document?.querySelector(anchorHash)
尚不存在..
尝试使用 window.onload
但其中的代码永远不会 运行..
有没有办法避免使用那个可怕的 setTimeout?
另请注意,锚点或锚定元素是可选的,所以我不知道如何使用回调引用
不要使用 document.querySelector
并且不要检查 class 名称,如果可以使用状态的话。
您根本不需要 setTimeout
,因为 useEffect
和 useEffectLayout
或多或少与 componentDidMount
:
相同
If you’re migrating code from a class component, note useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate. However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem. useLayoutEffect-Docs
我尝试将您的示例再减少一点,并使其在 codesandbox 中可调试(希望保持您的逻辑完整)。
但最重要的部分是以下内容:
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]);
您的组件将每次都被渲染,ref
、location
或 scrolled
变量已更改,但它应该只滚动到视图中,如果它没有以前做过。
这是我的组件的(非常)简化版本:
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;
};
如果没有 setTimeout,useLayoutEffect 中的代码将不会 运行 正确,因为组件未完全呈现并且 document?.querySelector(anchorHash)
尚不存在..
尝试使用 window.onload
但其中的代码永远不会 运行..
有没有办法避免使用那个可怕的 setTimeout?
另请注意,锚点或锚定元素是可选的,所以我不知道如何使用回调引用
不要使用 document.querySelector
并且不要检查 class 名称,如果可以使用状态的话。
您根本不需要 setTimeout
,因为 useEffect
和 useEffectLayout
或多或少与 componentDidMount
:
If you’re migrating code from a class component, note useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate. However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem. useLayoutEffect-Docs
我尝试将您的示例再减少一点,并使其在 codesandbox 中可调试(希望保持您的逻辑完整)。
但最重要的部分是以下内容:
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]);
您的组件将每次都被渲染,ref
、location
或 scrolled
变量已更改,但它应该只滚动到视图中,如果它没有以前做过。