在 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
中得到的参考
我有以下问题:
我有一个 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
中得到的参考