由于Gatsby为我们处理路线,这使得我们没有地方用Redux商店或提供商包装我们的应用程序。在这篇文章中,我们将学习一个聪明的技巧来解决这个问题。
为简单起见,所有示例都将使用React的上下文API](https://andsky.com/tech/tutorials/react-context-api)进行状态管理,以节省设置样板的时间,但所有内容仍然适用于其他状态管理方法。如果您需要温习一下与提供者的合作,您可以查看This Introo to the useContext hook.
安装
我更喜欢从一个非常基本的主题开始,但最后我们只需要两页的内容,这样我们就可以看到我们的状态正在全球范围内应用。因为我们将使用一些样式,所以我将使用node-sass
和gatsby-plugin-sass
添加对Sass的支持,因为我不是一只动物。
1$ gatsby new stateful-gatsby https://github.com/gatsbyjs/gatsby-starter-defaultCopyInstall
2$ cd stateful-gatsby
3$ yarn add node-sass gatsby-plugin-sass -D
使用Provider包装
第一步是为我们的上下文提供程序设置一个简单的‘isDark’状态和一个逆转其当前状态的方法。我们将把传入的任何东西作为道具,并将其包装在我们的新myConext.Provider
中。
1[label provider.js]
2import React, { useState } from 'react';
3
4export const myContext = React.createContext();
5
6const Provider = props => {
7 const [isDark, setTheme] = useState(false);
8
9 return (
10 <myContext.Provider value={{
11 isDark,
12 changeTheme: () => setTheme(!isDark)
13 }}>
14 {props.children}
15 </myContext.Provider>
16 )
17};
就在它下面,我们将导出一个函数,该函数将把传递给它的任何内容包装在我们的新提供程序中。
1export default ({ element }) => (
2 <Provider>
3 {element}
4 </Provider>
5);
现在我们有了一种管理状态的方法,Gatsby为我们提供了一个名为wrapRootElement‘的小钩子,您可以在[docs](https://www.gatsbyjs.org/docs/browser-apis/#wrapRootElement).中查看它这个钩子获取我们站点的大部分内容,并将其作为道具传递给我们提供给它的函数,就像我们刚刚从
Provider.js`中导出的函数一样,为我们提供了完美的小空间来包装其中的所有内容。
gatsby-browser.js
和gatsby-ssr.js
都可以访问这个钩子,建议用我们的提供者将它们都包装起来,这就是为什么我们在Provider.js
中定义了包装器函数。
1import Provider from './provider';
2
3export const wrapRootElement = Provider;
Style
以下是我们简单的主题风格:
1[label src/global.sass]
2.colorTheme
3 height: 100vh
4 transition: .3s ease-in-out
5
6.darkTheme
7 @extend .colorTheme
8 background-color: #1A202C
9 color: #fff
10 a
11 color: yellow
12
13.lightTheme
14 @extend .colorTheme
15 background-color: #fff
16 color: #000
应用主题
要访问我们的状态,我们唯一需要做的就是将每个组件包装在一个myConext.Consumer
中,并在Conext
上访问我们的全局状态,React.Fragment只是让我们添加多个元素。
对于我们的背景颜色,我们可以有条件地将我们的类设置为我们的状态,如果您有多个主题,您可以将提供者的主题设置为带有我们的类名的字符串。
1[label layout.js]
2import { myContext } from '../../provider';
3import '../global.sass';
4
5 return (
6 <myContext.Consumer>
7 {context => (
8 <React.Fragment>
9 <div className={context.isDark ? 'darkTheme' : 'lightTheme'}>
10 {/* ... */}
11 </div>
12 </React.Fragment>
13 )}
14 </myContext.Consumer>
15 )
我们还可以访问setTheme
,因为我们将其传递给了changeTheme
方法。让我们添加一个按钮来反转isDark
。
1[label src/pages/index.js]
2import { myContext } from '../../provider';
3
4const IndexPage = () => (
5 <Layout>
6 <myContext.Consumer>
7 {context => (
8 <React.Fragment>
9 <SEO title="Home" />
10 <h1>{context.isDark ? "Dark Theme" : "Light Theme"}</h1>
11
12 <button onClick={() => context.changeTheme()}>{context.isDark ? "Light" : "Dark"}</button>
13
14 <Link to="/page-2/">Go to page 2</Link>
15 </React.Fragment>
16 )}
17 </myContext.Consumer>
18 </Layout>
19);
现在,如果您将基本相同的配置添加到page-2.js
,您应该能够更改状态并在它们之间移动,而它们之间的状态保持一致。
1[label page-2.js]
2import { myContext } from '../../provider';
3
4const SecondPage = () => (
5 <Layout>
6 <myContext.Consumer>
7 {context => (
8 <React.Fragment>
9 <SEO title="Page two" />
10 <h1>{context.isDark ? "Dark Theme" : "Light Theme"}</h1>
11 <p>Welcome to page 2</p>
12
13 <button onClick={() => context.changeTheme()}>{context.isDark ? "Light" : "Dark"}</button>
14
15 <Link to="/">Go back to the homepage</Link>
16 </React.Fragment>
17 )}
18 </myContext.Consumer>
19 </Layout>
20);
哈扎!事情真的就这么简单,只需更改3个小文件,然后将所有内容包装在一个消费者中,您就可以开始了。
结论
对我来说,这不是一种非常明显的做事方式,所以我希望这有助于理解如何使用`wrapRootElement‘挂钩对您有利。
为了方便起见,我用这个设置创建了一个回购,您可以查看here.