在Gatsby中使用wrapRootElementHook进行状态管理

来自菜鸟教程
跳转至:导航、​搜索

由于 Gatsby 为我们处理我们的路线,这让我们无法使用 Redux 存储或提供程序 来包装我们的应用程序。 在本文中,我们将学习一个巧妙的技巧来解决这个问题。

为简单起见,所有示例都将使用 React 的 Context API 进行状态管理,以节省设置样板的时间,但一切仍然适用于其他状态管理方法。 如果您需要重新了解与提供程序的合作,您可以查看 this intro to the useContext hook

安装

我更喜欢从一个非常基本的主题开始,但最后我们只需要两页的东西,这样我们就可以看到我们的状态正在全球范围内应用。 由于我们将使用一些样式,因此我将使用 node-sassgatsby-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.jsgatsby-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 中并在 contextReact.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 钩子来发挥你的优势。

为了方便起见,我创建了一个使用此设置作为启动器的存储库,您可以 在此处查看