【发布时间】:2020-11-13 16:31:10
【问题描述】:
我创建了一个自定义挂钩,它将在重新加载和调整大小时检测设备。
import { useState, useEffect, useRef, useCallback } from "react";
export const DEVICE_SIZES_VALUE = {
MOBILE: 576,
TABLET: 768,
DESKTOP: 992,
LARGE_DESKTOP: 1200
};
export const DEVICE_TYPE = {
MOBILE_ONLY: "mobileOnly",
TABLET_ONLY: "tabletOnly",
UP_TO_TABLET: "upToTablet",
DESKTOP_ONLY: "desktopOnly"
};
const mobileOnly = () => window.innerWidth < DEVICE_SIZES_VALUE.MOBILE;
const tabletOnly = () =>
window.innerWidth > DEVICE_SIZES_VALUE.MOBILE &&
window.innerWidth < DEVICE_SIZES_VALUE.DESKTOP;
const upToTablet = () => window.innerWidth < DEVICE_SIZES_VALUE.DESKTOP;
const desktopOnly = () => window.innerWidth > DEVICE_SIZES_VALUE.DESKTOP;
const findViewType = deviceType => {
switch (deviceType) {
case DEVICE_TYPE.MOBILE_ONLY:
return mobileOnly();
case DEVICE_TYPE.TABLET_ONLY:
return tabletOnly();
case DEVICE_TYPE.UP_TO_TABLET:
return upToTablet();
case DEVICE_TYPE.DESKTOP_ONLY:
return desktopOnly();
default:
return false;
}
};
export const useView = type => {
const isInit = useRef(true);
const [isTypeFound, setIsTypeFound] = useState(false);
const handleResize = useCallback(() => {
setIsTypeFound(findViewType(type));
}, [type]);
useEffect(() => {
// To avoid window undefined error in SSR
if (process.browser) {
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}
}, [handleResize]);
useEffect(() => {
// will Skip this after first render
if (isInit && isInit.current) {
setIsTypeFound(findViewType(type));
isInit.current = false;
}
}, [isInit, type]);
return isTypeFound;
};
pages文件夹中的主路由文件是
import Home from "../Home";
export default function IndexPage() {
return (
<div>
<Home />
</div>
);
}
在主文件夹中
import { useView } from "../customHook";
const Home = () => {
const isTabletOnly = useView("upToTablet");
console.log("==rendering===", isTabletOnly);
return (
<>
{isTabletOnly && <p>this is responsive</p>}
<p>this is regular </p>
</>
);
};
export default Home;
问题:
- 如果您在平板电脑或移动设备视图上看到控制台,它将在刷新时记录两次,或者如果我从其他路线登陆该页面。这意味着两次渲染
- 我知道这是由于状态变化而发生的
问题:
- 有什么办法可以解决多重渲染问题吗?
- 按照我的写作方式,您能否向我推荐一些可以在此处完成的最佳实践。
注意:所有常量都将在单独的文件中。 SandBox Link
更新:
问题 1. 可以通过 @Drew Reese 所说的 useState(() => findViewType(type)) 解决。
不幸的是,出现了更多问题
问题:
-
在 SSR 期间
findViewType(type)将显示错误,因为未定义窗口 -
如果我通过使用
!process.browser绕过它,那么刷新时会出现此类问题
<div className={`class_1 ${isTabletOnly ? 'class_2' : 'class_3' }`}>
</div>
这会发出警告 SSR 和 CSR 不同。虽然isTabletOnly 是真的,但我的响应式相关类(class_2)不会附加,因为它将来自服务器端。
- 如果我像这样使用窗口错误绕过会有 eslint 警告
export const useView = (type) => {
if (!process.browser) return false;
// my rest of the code
}
【问题讨论】:
-
@DrewReese 感谢您的回答。我确实尝试在 next.config 中将严格模式设置为 false。但没有运气。
-
您也不应该在功能组件主体中直接控制台日志(或有任何其他副作用)。尝试将该日志放在
useEffect挂钩中,以查看它是否实际上 渲染了两次(来自更新)或没有(只是两次渲染调用)。 -
@DrewReese 抱歉回复晚了。我知道为什么会这样。一种。首先在服务器中渲染并将其设为 false。湾。然后首先在客户端渲染为假,因为初始状态为假。 C。 useView 使用新值传递类型和钩子 setState 并重新渲染。我一直在寻找有什么办法可以消除第二步。
-
在这种情况下监听调整大小是不好的,
window.matchMedia与更改监听器更有效。您可能想查看useMediaQuery
标签: reactjs react-hooks next.js