如何解决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 节点,并用它在客户端生成的节点替换它们。
有了这些信息,您就可以开始调试可能会导致意外内容闪现的原因以及如何解决它:
- 服务端,
windowDimension
为null
,null <= 740
为true,所以isMobile
设置为true - 服务器端生成的输出然后显示您希望移动访问者看到的
div>h1
元素 - 客户端,React 重新水化并触发
useEffect
钩子,第一个钩子调用状态设置器,传递一个大于或等于(大概)740 的数字,提示重新渲染 - 组件使用更新后的值重新渲染,
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 举报,一经查实,本站将立刻删除。