nextjs 自定义响应检测钩子调用了两次

Posted

技术标签:

【中文标题】nextjs 自定义响应检测钩子调用了两次【英文标题】:nextjs custom responsive detection hook called twice 【发布时间】: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 s-s-r
    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(() =&gt; findViewType(type)) 解决。

不幸的是,出现了更多问题

问题:

    在 s-s-r 期间 findViewType(type) 将显示错误,因为未定义窗口

    如果我通过使用!process.browser 绕过它,那么刷新时会出现此类问题

<div className=`class_1 $isTabletOnly ? 'class_2' : 'class_3' `>

</div>

这会发出警告 s-s-r 和 CSR 不同。虽然isTabletOnly 是真的,但我的响应式相关类(class_2)不会附加,因为它将来自服务器端。

    如果我像这样使用窗口错误绕过会有 eslint 警告
export const useView = (type) => 
  if (!process.browser) return false;
  // my rest of the code

【问题讨论】:

这能回答你的问题吗? Why does useState cause the component to render twice on each update? @DrewReese 感谢您的回答。我确实尝试在 next.config 中将严格模式设置为 false。但没有运气。 您也不应该在功能组件主体中直接控制台日志(或有任何其他副作用)。尝试将该日志放在useEffect 挂钩中,以查看它是否实际上 渲染了两次(来自更新)或没有(只是两次渲染调用)。 @DrewReese 抱歉回复晚了。我知道为什么会这样。一种。首先在服务器中渲染并将其设为 false。湾。然后首先在客户端渲染为假,因为初始状态为假。 C。 useView 使用新值传递类型和钩子 setState 并重新渲染。我一直在寻找有什么办法可以消除第二步。 在这种情况下监听调整大小是不好的,window.matchMedia 与更改监听器更有效。您可能想查看useMediaQuery 【参考方案1】:

我相信您可以使用状态初始化函数将前两个渲染压缩为初始渲染。

Lazy initial state

initialState 参数是初始渲染期间使用的状态。 在随后的渲染中,它被忽略。如果初始状态是 昂贵计算的结果,您可以提供一个函数 而是只在初始渲染时执行

const [isTypeFound, setIsTypeFound] = useState(
  () => findViewType(type) // <-- pass the type prop
);

这将运行初始渲染并提供您想要的实际组件安装状态。

【讨论】:

太棒了。非常感谢。我完全忘记了这一点。这将与 reactjs 项目完美配合。不幸的是,NextJs 没有。我已经更新了我的问题。 @MonzoorTamal 我在想也许你可以改用useLayoutEffect,但是文档有一个很好的技巧,使用 useEffect 和 s-s-r 有条件地隐藏 UI 直到它被水合:reactjs.org/docs/hooks-reference.html#uselayouteffect 也许这个'会帮助你一点。

以上是关于nextjs 自定义响应检测钩子调用了两次的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security 自定义 AuthenticationProvider 验证方法调用了两次

自定义 UITableViewCell 创建了两次......让我发疯

iOS - drawRect:调用了两次

RequestImage 的结果处理程序调用了两次

ReactJs/NextJs useEffect() 总是被调用两次

UITableViewCell setSelected:animated 在 iPad 上调用了两次