Gatsbyがルートを処理するため、Reduxストアまたはプロバイダーでアプリをラップする場所がありません。 この記事では、それを回避するための巧妙なトリックを学びます。

簡単にするために、すべての例では、状態管理にReactの Context API を使用して、ボイラープレートの設定にかかる時間を節約していますが、他の状態管理方法にもすべて適用できます。 プロバイダーとの連携をブラッシュアップする必要がある場合は、useContextフックこのイントロを確認できます。

インストール

私は非常に基本的なテーマから始めることを好みますが、最終的には2ページの何かが必要なだけなので、私たちの状態がグローバルに適用されていることがわかります。 いくつかのスタイリングで作業するので、私は動物ではないので、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でラップします。

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と呼ばれる小さなフックを提供します。これは、docsで確認できます。 このフックは、サイトの大部分を取得し、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のグローバル状態にアクセスすることだけです。複数の要素を追加します。

背景色については、条件付きでクラスを状態に設定できます。複数のテーマがある場合は、プロバイダーのテーマをクラス名の文字列として設定できます。

layout.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>
  )

changeThemeメソッドに渡したため、setThemeにもアクセスできます。 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>
);

image description

ふざ! それは本当にそれと同じくらい簡単です。3つの小さなファイルの変更とすべてをコンシューマーでラップするので、準備は完了です。

結論

私にとって、これは物事を行うための非常に明白な方法ではなかったので、これがwrapRootElementフックを有利に使用する方法を理解するのに役立つことを願っています。

便宜上、このセットアップをスターターとして使用してリポジトリを作成しました。これは、ここでチェックアウトできます