【问题标题】:CSS style is not correctly being applied when conditional render in React在 React 中条件渲染时未正确应用 CSS 样式
【发布时间】:2021-01-03 18:35:27
【问题描述】:

我需要根据屏幕大小有条件地渲染组件。

我使用 nextjs 和 getInitialProps 来获取数据,页面是服务器端渲染的。我想在客户端检测设备的屏幕大小,所以我实现了一个自定义的钩子来做到这一点。

使用WindowSize.js

import { useEffect, useState } from 'react';

export default function useWindowSize() {
    const [windowSize, setWindowSize] = useState({
        width: typeof window === 'undefined' ? 1200 : window.innerWidth, // default width 1200
    });

    useEffect(() => {
        // Handler to call on window resize
        function handleResize() {
            // Set window width/height to state
            setWindowSize({
                width: window.innerWidth,
                //height: window.innerHeight,
            });
        }

        // Add event listener
        window.addEventListener('resize', handleResize);

        // Call handler right away so state gets updated with initial window size
        handleResize();

        // Remove event listener on cleanup
        return () => window.removeEventListener('resize', handleResize);
    }, []); // Empty array ensures that effect is only run on mount

    return windowSize.width <= 600;
}

然后我使用这个钩子来检测窗口大小和条件渲染组件

export default function IndexPage() {
  const isMobile = useWindowSize();

  if (typeof window !== "undefined") {
    // if you are running it on codesanbox, I don't know why log is not printed
    console.log("client side re-render");
  }

  return (
    <div>
      {isMobile ? (
        <div
          style={{
            color: "red",
            fontSize: 40
          }}
        >
          mobile
        </div>
      ) : (
        <div
          style={{
            color: "blue",
            fontSize: 20
          }}
        >
          desktop
        </div>
      )}
    </div>
  );
}

IndexPage.getInitialProps = () => {
  return {
    a: 1
  };
};

当我在移动浏览器上加载页面时,你会看到

text mobile 应用了错误的 CSS 样式。视频演示:https://share.getcloudapp.com/nOuk08L0

如何重现: https://codesandbox.io/s/thirsty-khayyam-npqpt

谁能帮帮我。提前谢谢!

【问题讨论】:

    标签: css reactjs next.js


    【解决方案1】:

    感谢@Andrew Zheng 的详细解释!今天学到了。

    我知道我可以使用纯 CSS 媒体查询来设置布局样式,但我的用例需要像 isMobile 这样的变量来

    if (isMobile) {
        doSomethingOnlyOnMobileWeb();
    } else {
        doSomethingOnlyForDesktopWeb();
    }
    

    所以我结合了你提供的两种方法,并以这种方式修改我的钩子:

    export default function useWindowSize(userAgent) {
        let isMobile = Boolean(
            userAgent &&
                userAgent.match(
                    /Android|BlackBerry|iPhone|iPod|Opera Mini|IEMobile|WPDesktop/i
                )
        );
        
        const [windowSize, setWindowSize] = useState({
            width: isServer
                ? isMobile
                    ? BREAKPOINT_SMALL
                    : BREAKPOINT_LARGE
                : window.innerWidth,
            
        });
    
        useEffect(() => {
            // Handler to call on window resize
            function handleResize() {
                // Set window width/height to state
                setWindowSize({
                    width: window.innerWidth,
                    //height: window.innerHeight,
                });
            }
    
            // Add event listener
            window.addEventListener('resize', handleResize);
    
            // Call handler right away so state gets updated with initial window size
            handleResize();
    
            // Remove event listener on cleanup
            return () => window.removeEventListener('resize', handleResize);
        }, []); // Empty array ensures that effect is only run on mount
    
        return windowSize.width <= BREAKPOINT_SMALL;
    }
    

    diff:将用户代理字符串传递给 useWindowSize 进行服务器端检测,使用 window.innerWidth 进行客户端检测。服务器和客户端之间不会不匹配。

    【讨论】:

    • 很高兴你找到了答案!
    【解决方案2】:

    这是一个与 React 如何从 SSR 修补 DOM 相关的问题。当客户端和服务器端渲染不匹配时,React 只会修补/同步节点的文本上下文。 DOM 属性不会自动更新。在您的情况下,SSR 结果具有桌面样式,因为没有 window 对象,而客户端具有移动结果。不匹配后,React 将文本节点从 'desktop' 更新为 mobile,但不更新样式属性。

    在我看来,您可以使用两种不同的方法。您可以使用 Media Query 根据屏幕宽度而不是钩子来设置组件样式。如果您正在执行 SSR,而不是 SSG,您可以使用用户代理 req.headers["user-agent"] 来检测正在查看您的设备的设备。

    对于第一种方法,您可能需要渲染更多可能需要的 DOM 节点。对于第二种方法,您将无法知道实际的视口大小,这可能会导致视觉问题。您或许可以将这两种方法结合起来为您的用户提供良好的观看体验。

    参考

    https://github.com/facebook/react/issues/11128#issuecomment-334882514

    【讨论】: