在 gatsby 生产模式下,React Hook 在第一次渲染时无法正常工作

React Hook does not work properly on the first render in gatsby production mode

我有以下问题:

我有一个 gatsby 网站,在 js 中使用 emotion 作为 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 值,OS 颜色方案,默认为 false。如果我 运行 应用程序,并激活暗模式,则设置 localStorage 值。如果我现在重新加载应用程序,getInitialIsDark 方法 returns 为真,但 UI 呈现浅色主题。在明暗之间来回切换按预期工作,只是初始负载不起作用。

如果我用 true 替换 getInitialIsDark 加载 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>
  )
}

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

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

你试过这样设置你的初始状态吗?

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

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

useState(() => getInitialIsDark())

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

中是否存在

希望对您有所帮助!

@PedroFilipe 是正确的,useState(() => getInitialIsDark()) 不是在启动时调用检查功能的方法。表达式 () => getInitialIsDark() 是真实的,因此根据 <ThemedLayout isDark={isDark}> 使用道具的方式,它可能会意外工作,但 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])

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

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

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

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

第一个

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

中得到的参考