很长一段时间以来,我一直在使用一个名为
useIsMobile
的自定义反应钩子,如果视口低于特定宽度,它会返回 true。我在所有项目中都使用了这个钩子,并且从未遇到过任何问题。
最近我使用
Next 14
启动了一个新项目,我开始收到此错误:
Error: Text content does not match server-rendered HTML. See more info here: https://nextjs.org/docs/messages/react-hydration-error
经过一些调试,我意识到问题出在我的自定义挂钩上。当我使用钩子时,服务器首先发送“移动版本”,但客户端按应有的方式渲染“桌面版本”,并在从服务器预渲染的反应树和从服务器预渲染的反应树之间产生差异。在第一次渲染期间渲染。
即使我使用“使用客户端”,此问题仍然存在。 我还注意到,偶尔,CSS 媒体查询可能会触发错误,或者它们可能会被完全忽略。
还有其他人遇到过这个问题吗?有谁知道我该如何解决这个问题?
这是我使用的钩子:
'use client';
import { useState, useEffect } from 'react';
const useWindowSize = () => {
const [windowSize, setWindowSize] = useState({
width: typeof window !== 'undefined' ? window?.innerWidth : null,
height: typeof window !== 'undefined' ? window?.innerHeight : null,
});
useEffect(() => {
const handleResize = () => {
if (typeof window !== 'undefined') {
setWindowSize({
width: window?.innerWidth,
height: window?.innerHeight,
});
}
};
window?.addEventListener('resize', handleResize);
handleResize();
return () => window?.removeEventListener('resize', handleResize);
}, []);
return windowSize;
};
export default useWindowSize;
'use client';
import { useState, useEffect } from 'react';
import useWindowSize from './useWindowSize';
const useIsMobile = (breakpoint: number = 768): boolean => {
const { width } = useWindowSize()!;
const [isMobile, setMobile] = useState<boolean>(
width! <= (breakpoint || 768)
);
useEffect(() => {
setMobile(width! <= (breakpoint || 768));
}, [width, breakpoint]);
return isMobile;
};
export default useIsMobile;
const isMobile = useIsMobile(500);
return (
<div className={styles.nav}>
{!isMobile && <div className={styles.logo}>Logo</div>}
解决方案是保持渲染元素,直到客户端评估设备宽度。
'use client';
import { useState, useEffect, useLayoutEffect } from 'react';
import useWindowSize from './useWindowSize';
const useIsMobile = (breakpoint = 768) => {
const { width } = useWindowSize();
const [isMobile, setMobile] = useState(null); // starts as null to indicate no initial assumption
useLayoutEffect(() => {
if (width !== null) {
setMobile(width <= breakpoint);
}
}, [width, breakpoint]);
return isMobile;
};
export default useIsMobile;
在您的组件中,您可以根据是否已确定
isMobile
有条件地渲染内容:
const isMobile = useIsMobile(500);
if (isMobile === null) {
return <div>Loading...</div>;
}
return (
<div className={styles.nav}>
{!isMobile && <div className={styles.logo}>Logo</div>}
</div>
);