在Gatsby中使用wrapRootElementHook进行状态管理
由于 Gatsby 为我们处理我们的路线,这让我们无法使用 Redux 存储或提供程序 来包装我们的应用程序。 在本文中,我们将学习一个巧妙的技巧来解决这个问题。
为简单起见,所有示例都将使用 React 的 Context API 进行状态管理,以节省设置样板的时间,但一切仍然适用于其他状态管理方法。 如果您需要重新了解与提供程序的合作,您可以查看 this intro to the useContext hook。
安装
我更喜欢从一个非常基本的主题开始,但最后我们只需要两页的东西,这样我们就可以看到我们的状态正在全球范围内应用。 由于我们将使用一些样式,因此我将使用 node-sass 和 gatsby-plugin-sass 添加 Sass 支持,因为我不是动物。
$ gatsby new stateful-gatsby https://github.com/gatsbyjs/gatsby-starter-defaultCopyInstall $ cd stateful-gatsby $ yarn add node-sass gatsby-plugin-sass -D
用提供者包装
第一步是使用简单的 isDark 状态和反转其当前状态的方法设置我们的上下文提供程序。 我们将传入的任何内容作为道具并将其包装在我们新的 myContext.Provider 中。
提供者.js
import React, { useState } from 'react';
export const myContext = React.createContext();
const Provider = props => {
const [isDark, setTheme] = useState(false);
return (
<myContext.Provider value={{
isDark,
changeTheme: () => setTheme(!isDark)
}}>
{props.children}
</myContext.Provider>
)
};
在它的下方,我们将导出一个函数,该函数将在我们的新提供者中包装传递给它的任何内容。
export default ({ element }) => (
<Provider>
{element}
</Provider>
);
现在我们有了管理状态的方法,Gatsby 为我们提供了一个简洁的小钩子,称为 wrapRootElement,您可以在 文档 中查看它。 这个钩子占用了我们网站的大部分内容,并将其作为道具传递给我们提供给它的函数,就像我们刚刚从 Provider.js 导出的函数一样,为我们提供了完美的小空间来包装所有内容。
gatsby-browser.js 和 gatsby-ssr.js 都可以访问这个钩子,建议用我们的提供者包装它们,这就是我们在 provider.js 中定义包装函数的原因。
import Provider from './provider'; export const wrapRootElement = Provider;
造型
以下是我们简单的主题样式:
src/global.sass
.colorTheme
height: 100vh
transition: .3s ease-in-out
.darkTheme
@extend .colorTheme
background-color: #1A202C
color: #fff
a
color: yellow
.lightTheme
@extend .colorTheme
background-color: #fff
color: #000
应用主题
要访问我们的状态,我们唯一需要做的就是将每个组件包装在 myContext.Consumer 中并在 context、React.Fragment 上访问我们的全局状态只是为了让我们添加多个元素。
对于我们的背景颜色,我们可以有条件地将我们的类设置为我们的状态,如果您有多个主题,您可以将提供者的主题设置为带有我们类名的字符串。
布局.js
import { myContext } from '../../provider';
import '../global.sass';
return (
<myContext.Consumer>
{context => (
<React.Fragment>
<div className={context.isDark ? 'darkTheme' : 'lightTheme'}>
{/* ... */}
</div>
</React.Fragment>
)}
</myContext.Consumer>
)
我们还可以访问 setTheme 因为我们将它传递给了 changeTheme 方法。 让我们添加一个按钮来反转 isDark。
src/pages/index.js
import { myContext } from '../../provider';
const IndexPage = () => (
<Layout>
<myContext.Consumer>
{context => (
<React.Fragment>
<SEO title="Home" />
<h1>{context.isDark ? "Dark Theme" : "Light Theme"}</h1>
<button onClick={() => context.changeTheme()}>{context.isDark ? "Light" : "Dark"}</button>
<Link to="/page-2/">Go to page 2</Link>
</React.Fragment>
)}
</myContext.Consumer>
</Layout>
);
现在,如果您向 page-2.js 添加基本相同的配置,您应该能够更改状态并在它们之间移动,并且它们之间的状态保持一致。
page-2.js
import { myContext } from '../../provider';
const SecondPage = () => (
<Layout>
<myContext.Consumer>
{context => (
<React.Fragment>
<SEO title="Page two" />
<h1>{context.isDark ? "Dark Theme" : "Light Theme"}</h1>
<p>Welcome to page 2</p>
<button onClick={() => context.changeTheme()}>{context.isDark ? "Light" : "Dark"}</button>
<Link to="/">Go back to the homepage</Link>
</React.Fragment>
)}
</myContext.Consumer>
</Layout>
);
胡扎! 它真的就这么简单,3 个小文件更改并将所有内容包装在一个消费者中,你就可以开始了。
结论
对我来说,这不是一种非常明显的做事方式,所以我希望这有助于理解如何使用 wrapRootElement 钩子来发挥你的优势。
为了方便起见,我创建了一个使用此设置作为启动器的存储库,您可以 在此处查看 。