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

时间:2020-07-23 20:46:32

标签: reactjs react-hooks next.js

我创建了一个自定义钩子,可以在重新加载并调整大小时检测设备。

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;

问题:

  • 如果您在平板电脑或移动设备视图上看到该控制台,它将刷新记录两次,或者如果我从其他途径进入该页面。这意味着两次渲染
  • 我知道这是由于状态变化而发生的

问题:

  1. 是否可以解决该多重渲染问题?
  2. 我的写作方式可以建议我一些可以在此处完成的最佳实践。

注意:所有常量将在单独的文件中。 SandBox Link

更新:

第1个问题可以通过useState(() => findViewType(type))来解决,正如@Drew Reese所说。

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

问题:

  1. 在SSR findViewType(type)期间将显示错误,因为未定义窗口

  2. 如果我通过使用!process.browser绕过该问题,则此类问题将在刷新时出现

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

</div>

这将发出警告SSR和CSR不同。尽管isTabletOnly是正确的,但我的响应式相关类(class_2)不会追加,因为它来自服务器端。

  1. 如果我使用像这样的窗口错误旁路功能,则会出现eslint警告
export const useView = (type) => {
  if (!process.browser) return false;
  // my rest of the code
}

1 个答案:

答案 0 :(得分:2)

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

Lazy initial state

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

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

这将在初始渲染时运行,并提供所需的实际组件安装状态。