【问题标题】:React Hook does not work properly on the first render in gatsby production modeReact Hook 在 gatsby 生产模式下的第一次渲染时无法正常工作
【发布时间】:2020-08-28 16:53:55
【问题描述】:

我有以下问题:

我有一个gatsby 网站,它使用emotion 用于js 中的css。我使用emotion theming 来实现暗模式。当我运行gatsby develop 时,暗模式按预期工作,但如果我用gatsby build && gatsby serve 运行它,它就不起作用。更具体地说,暗模式只有在切换到亮并再次返回后才能工作。

我必须关注处理主题的顶级组件:

const Layout = ({ children }) => {
  const [isDark, setIsDark] = useState(() => getInitialIsDark())

  useEffect(() => {
    if (typeof window !== "undefined") {
      console.log("save is dark " + isDark)
      window.localStorage.setItem("theming:isDark", isDark.toString())
    }
  }, [isDark])

  return (
    <ThemeProvider theme={isDark ? themeDark : themeLight}>
      <ThemedLayout setIsDark={() => setIsDark(!isDark)} isDark={isDark}>{children}</ThemedLayout>
    </ThemeProvider>
  )
}

getInitalIsDark 函数检查 localStorage 值、操作系统颜色方案,默认为 false。如果我运行应用程序并激活暗模式,则设置 localStorage 值。如果我现在重新加载应用程序,getInitialIsDark 方法返回 true,但 UI 呈现浅色主题。在明暗之间来回切换按预期工作,只是初始负载不起作用。

如果我将 getInitialIsDark 替换为 true,加载 darkMode 会按预期工作,但 lightMode 已损坏。我让它工作的唯一方法是使用以下代码在按时加载后自动重新渲染。

const Layout = ({ children }) => {
  const [isDark, setIsDark] = useState(false)
  const [isReady, setIsReady] = useState(false)

  useEffect(() => {
    if (typeof window !== "undefined" && isReady) {
      console.log("save is dark " + isDark)
      window.localStorage.setItem("theming:isDark", isDark.toString())
    }
  }, [isDark, isReady])

  useEffect(() => setIsReady(true), [])
  useEffect(() => {
    const useDark = getInitialIsDark()
    console.log("init is dark " + useDark)
    setIsDark(useDark)
  }, [])

  return (
    <ThemeProvider theme={isDark ? themeDark : themeLight}>
      {isReady ? (<ThemedLayout setIsDark={() => setIsDark(!isDark)} isDark={isDark}>{children}</ThemedLayout>) : <div/>}
    </ThemeProvider>
  )
}

但这会导致页面加载时出现难看的闪烁。

第一种方法中的钩子我做错了什么,初始值没有按我的预期工作。

【问题讨论】:

  • 如何呈现布局提供程序。你是使用wrapRootElement还是直接在每个页面中添加?
  • @ShubhamKhatri 我使用gatsby-plugin-layout 将所有页面包装到布局提供程序中。
  • isReady 似乎毫无意义——你能通过改变 orf 1st 和 3rd useEffect 的顺序来做同样的事情吗?并且控制台日志可能会导致闪烁。最好的方法是预取并传入 isDark 的值。

标签: reactjs react-hooks gatsby emotion


【解决方案1】:

您是否尝试像这样设置您的初始状态?

const [isDark, setIsDark] = useState(getInitialIsDark())

请注意,我没有将 getInitialIsDark() 包装在附加函数中:

useState(() =&gt; getInitialIsDark())

您可能会崩溃您的构建,因为localStorage 未在构建时定义。您可能需要检查 getInitialIsDark 中是否存在。

希望这会有所帮助!

【讨论】:

  • 感谢您的建议,我直接使用 getInitialIsDark 进行了测试。如果在构建时未定义 localStorage,它不会崩溃,而是该函数默认为 false,如问题中所述。
  • 我重读了这个问题,看到你提到了这一点。对不起这是我的错。如果getInitialIsDark 默认为false,难道是当 gatsby 构建并且找不到 localStorage 时它只返回值falseundefined?您可以改为在挂载时调用 useEffect 内的函数 getInitialIsDark,在组件挂载后使用正确的值“更新”状态。
【解决方案2】:

@PedroFilipe 是正确的,useState(() =&gt; getInitialIsDark()) 不是在启动时调用检查功能的方式。表达式() =&gt; getInitialIsDark() 是真实的,因此取决于&lt;ThemedLayout isDark={isDark}&gt; 如何使用道具,它可能会意外工作,但useState 不会评估传入的函数(据我所知)。

当使用初始值 const [myValue, setMyValue] = useState(someInitialValue) 时,myValue 中看到的值可能会滞后。我不知道为什么,但这似乎是钩子问题的常见原因。

如果组件 always 多次渲染(例如其他东西是异步的),则不会出现问题,因为在第二次渲染中,变量将具有预期值。

为了确保在启动时检查 localstorage,您需要一个额外的 useEffect() 来显式调用您的函数。

useEffect(() => {
  setIsDark(getInitialIsDark());
}, [getInitialIsDark]); //dependency only needed to satisfy linter, essentially runs on mount.

尽管大多数useEffect 示例使用匿名函数,但您可能会发现使用命名函数更容易理解(遵循使用函数名称进行文档的简洁代码原则)

useEffect(function checkOnMount() {
  setIsDark(getInitialIsDark());
}, [getInitialIsDark]);   

useEffect(function persistOnChange() {
  if (typeof window !== "undefined" && isReady) {
    console.log("save is dark " + isDark)
    window.localStorage.setItem("theming:isDark", isDark.toString())
  }
}, [isDark])

【讨论】:

  • 如果我按照描述使用它,我需要引入另一个状态或 ref 以确保不使用初始静态值调用 persisitOnChange 部分。这最终或多或少地导致了我的第二个解决方案 - 这确实有效,但会导致 UI 闪烁。也许没有闪烁就没有办法实现这一点。我发现了多篇使用State(() => ...) 的博客文章。看来他们都错了。
  • 这不是关于状态,而是关于将事件分成单独的useEffect() 调用。 WRT闪烁,最好的选择可能是在App中调用getInitialIsDark()并将结果作为道具传递。
  • 请注意,不管有多少其他 useEffect,persistOnChange 都会运行。你只需要弄清楚函数内部的逻辑,或者相反,你为什么关心 localstorage 是否更新为相同的值?
【解决方案3】:

我遇到了一个类似的问题,其中一些样式没有生效,因为它们是通过在安装时设置的类应用的(就像你只在生产构建中一样,在开发中一切正常)。

我最终将 React 使用的 hydrate 函数从 ReactDOM.hydrate 切换到 ReactDOM.render 并且问题消失了。

// gatsby-browser.js
export const replaceHydrateFunction = () => (element, container, callback) => {
  ReactDOM.render(element, container, callback);
};

【讨论】:

  • 这对解决我的问题也很有帮助。我有一个包含组件代码拆分的站点(对移动设备使用 X 组件,对桌面使用 Y 组件)。 SSR 默认使用移动组件,因为在 SSR 时不知道窗口视口。标准的 ReactDOM.hydrate 方法在桌面重新水合期间静默但视觉上失败,因为移动 HTML 存在,桌面 HTML 是预期的。这个答案解决了我的问题。谢谢!
【解决方案4】:

这对我有用,试试这个,如果成功,请告诉我。

首先

src/components/ 我创建了一个组件 navigation.js

    export default class Navigation extends Component {
      static contextType = ThemeContext // eslint-disable-line
      render() {
        const theme = this.context  
        return (
          <nav className={'nav scroll' : 'nav'}>
            <div className="nav-container">
               <button
                 className="dark-switcher"
                 onClick={theme.toggleDark}
                 title="Toggle Dark Mode"
                >
              </button>
            </div>
          </nav>
        )
      }
    }

第二

创建了一个 gatsby-browser.js

    import React from 'react'
    import { ThemeProvider } from './src/context/ThemeContext'

    export const wrapRootElement = ({ element }) => <ThemeProvider>{element}</ThemeProvider>

第三

我在 src/context/ 中创建了一个 ThemeContext.js 文件

    import React, { Component } from 'react'

    const defaultState = {
      dark: false,
      notFound: false,
      toggleDark: () => {},
    }

    const ThemeContext = React.createContext(defaultState)

    class ThemeProvider extends Component {
      state = {
        dark: false,
        notFound: false,
      }

      componentDidMount() {
        const lsDark = JSON.parse(localStorage.getItem('dark'))

        if (lsDark) {
          this.setState({ dark: lsDark })
        }
      }

      componentDidUpdate(prevState) {
        const { dark } = this.state

        if (prevState.dark !== dark) {
          localStorage.setItem('dark', JSON.stringify(dark))
        }
      }

      toggleDark = () => {
        this.setState(prevState => ({ dark: !prevState.dark }))
      }

      setNotFound = () => {
        this.setState({ notFound: true })
      }

      setFound = () => {
        this.setState({ notFound: false })
      }

      render() {
        const { children } = this.props
        const { dark, notFound } = this.state

        return (
          <ThemeContext.Provider
            value={{
              dark,
              notFound,
              setFound: this.setFound,
              setNotFound: this.setNotFound,
              toggleDark: this.toggleDark,
            }}
          >
            {children}
          </ThemeContext.Provider>
        )
      }
    }

    export default ThemeContext

    export { ThemeProvider }

这应该对你有用,这是我从official Gatsby site 中遵循的参考

【讨论】:

    猜你喜欢
    • 2022-10-17
    • 1970-01-01
    • 2021-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-26
    • 1970-01-01
    相关资源
    最近更新 更多