微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

React/Gatsby:条件渲染时会在短时间内加载错误的 div

如何解决React/Gatsby:条件渲染时会在短时间内加载错误的 div

我正在尝试在 Gatsby 中有条件地呈现一个 div,以构建响应式导航菜单。不幸的是,就在完整的导航菜单加载之前,我快速浏览了菜单 div。任何解决此问题的提示或技巧将不胜感激!

    import React,{ useEffect,useState } from "react"
    import * as navlinksstyles from "./navlinks.module.scss"
    
    const NavLinks = () => {
      const [windowDimension,setwindowDimension] = useState(null)
    
      useEffect(() => {
        setwindowDimension(window.innerWidth)
      },[])
    
      useEffect(() => {
        function handleResize() {
          setwindowDimension(window.innerWidth)
        }
    
        window.addEventListener("resize",handleResize)
        return () => window.removeEventListener("resize",handleResize)
      },[])
    
      const isMobile = windowDimension <= 740
    
      return (
        <div className={navlinksstyles.wrapper}>
          {isMobile ? (
            <div>
              <h1 className={navlinksstyles.menu}>menu</h1>
            </div>
          ) : (
            <div className={navlinksstyles.navlinksWrapper}>
              <ul>
                <li>About</li>
                <li>Services</li>
                <li>Frames</li>
                <li>Lenses</li>
                <li>Locations</li>
                <li>MailBox</li>
              </ul>
            </div>
          )}
        </div>
      )
    }
    
    export default NavLinks

解决方法

您的页面使用 windowDimension 作为 null 呈现,因此当具有更宽 windowDimension 的人访问您的页面时,他们会在 React 启动之前看到短暂的移动布局闪烁 &渲染正确的。

你可以通过使用@media 查询来解决这个问题。

,

Gatsby 进行服务器端渲染,这涉及在 Node 环境中渲染您的 React 组件并将生成的标记保存为静态文件。当有人访问您在生产中的页面(或者在开发中,如果您在开发中使用 SSR),React 会呈现您的组件并将它们与已经可见的 DOM 节点关联,这个过程称为“再水化”。

了解这一点很重要,因为 useEffect(或基于类的 API 方法,如 componentDidMount)不会在 SSR 期间运行。它们仅在代码在客户端重新水合后运行。此外,如果服务器端生成的 DOM 节点与 React 在初始渲染时(在任何 useEffect 钩子运行之前)在客户端渲染的内容不匹配,您最终会遇到水化不匹配错误,提示 React丢弃存在的 DOM 节点,并用它在客户端生成的节点替换它们。

有了这些信息,您就可以开始调试可能会导致意外内容闪现的原因以及如何解决它:

  1. 服务端,windowDimensionnullnull <= 740为true,所以isMobile设置为true
  2. 服务器端生成的输出然后显示您希望移动访问者看到的 div>h1 元素
  3. 客户端,React 重新水化并触发 useEffect 钩子,第一个钩子调用状态设置器,传递一个大于或等于(大概)740 的数字,提示重新渲染
  4. 组件使用更新后的值重新渲染,isMobile 设置为 false,将输出更新为完整的导航菜单

解决此问题的一种方法是等待呈现标记或呈现占位符,直到您在浏览器中呈现:

// Note: do NOT do this!
const NavLinks = () => {
  if (typeof window === "undefined") return null
  
  return (
    <div>Your content</div>
  )
}

上面提到的问题在于,您最终会在服务器端生成与在浏览器中生成的标记不同的标记,从而导致 React 丢弃 DOM 节点并替换它们。相反,您可以像这样利用 useEffect 来确保初始渲染与服务器端输出匹配:

const NavLinks = () => {
  const [ready,setReady] = useState(null)
  useEffect(() => { setReady(true) },[])
  
  // note: the return value of an `&&` expression is the value of the first
  // falsey condition,or the last condition if all are truthy,so if `ready`
  // has not been updated,this evaluates to `return null`,and otherwise,to
  // return <div>Your content</div>.
  return ready && <div>Your content</div>
}

还有另一种方法可以在许多情况下工作,可以避免额外的渲染、DOM 更新/布局/绘制周期:使用 CSS 隐藏一个相关的 DOM 分支:

/** @jsx jsx */
import { jsx } from "@emotion/core"

const mobile = "@media (max-width: 740px)"

const NavLinks = () => (
  <div>
    <div css={{ display: "none",[mobile]: { display: "block" } }}>
      Mobile menu
    </div>
    <div css={{ [mobile]: { display: "none" } }}>
      Desktop menu
    </div>
  </div>
)

如果可能,我更喜欢这种方法,因为它减少了加载时布局抖动的情况,但是如果 DOM 树很大,额外的标记也会减慢速度。与任何事情一样,请针对您自己的用例进行一些测试,然后选择最适合您和您的团队的方式。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。