Gatsby(with TypeScript)にReduxを組み込む

そもそもいるのかという疑問はあるかもしれませんが、チャレンジ要素は絞りたいのでreduxつかいます。 ライブラリインストール GatsbyでRedux使う場合も、インストールするものは同じです。 Reducerなどを作る […]

広告ここから
広告ここまで

目次

    そもそもいるのかという疑問はあるかもしれませんが、チャレンジ要素は絞りたいのでreduxつかいます。

    ライブラリインストール

    GatsbyでRedux使う場合も、インストールするものは同じです。

    $ yarn add redux react-redux
    
    // TS使うなら
    $ yarn add -D @types/react-redux

    Reducerなどを作る

    定番ですが、カウンターをReduxで作っておきます。Ducksで管理するのが楽なので、src/modules/index.tsに以下のコードをまとめました。

    type Actions = ReturnType<typeof add> |
      ReturnType<typeof reduce> |
      ReturnType<typeof reset>
    
    export type State = {
      count: number;
    }
    
    /**
     * Action Types
     */
    export const ADD = 'ADD' as const
    export const REDUCE = 'REDUCE' as const
    export const RESET = 'RESET' as const
    
    /**
     * Actions
     */
    export const add = (num: number) => ({
      type: ADD,
      payload: {
        num
      }
    })
    export const reduce = (num: number) => ({
      type: REDUCE,
      payload: {
        num
      }
    })
    export const reset = () => ({
      type: RESET
    })
    
    /**
     * Reducer
     */
    const initialState: State = {
      count: 0
    }
    export default (state: State = initialState, action: Actions) => {
      switch(action.type) {
        case ADD: {
          return {
            count: state.count + action.payload.num
          }
        }
        case REDUCE: {
          return {
            count: state.count - action.payload.num
          }
        }
        case RESET:
          return initialState
        default:
          return state;
      }
    }

    Storeの作成

    続いてStoreを作ります。src/store/index.tsに実装しました。

    import { combineReducers, createStore } from 'redux';
    import firstReducer from '../modules/index'
    
    
    const reducer = combineReducers<{
      first: ReturnType<typeof testReducer>
    }>({
      first: firstReducer
    })
    
    export type State = ReturnType<typeof reducer>
    
    export default () => createStore(reducer)

    Reduxを使う場合の問題点

    最後にProviderにStoreを入れれば終わり・・・なのですが、Gatsbyの場合はProviderをどこに書けばいいのかという問題があります。

    CRAであれば一般的にsrc/App.jsxのようなファイル必ず読み込まれますので、ここにProviderを入れておけばOKです。ですが、GatsbyやNext.jsなどの場合はページ毎に読み込まれるファイルが変わりますので、別の方法を使います。

    wrapRootElementを使う

    Gatsbyの場合、wrapRootElementというAPIが提供されています。これを使うことで、Reduxに限らずProvider系をアプリケーション全体に設定することができます。

    wrapRootElement (Function)

    Allow a plugin to wrap the root element.
    This is useful to set up any Provider components that will wrap your application. For setting persistent UI elements around pages use wrapPageElement.
    Note: There is an equivalent hook in Gatsby’s SSR API. It is recommended to use both APIs together. For example usage, check out Using redux.

    https://www.gatsbyjs.org/docs/browser-apis/#wrapRootElement

    src/wrap-with-provider.tsxの実装

    browserとssrどちらでも設定するため、1ファイルに記述をまとめます。

    import React from 'react'
    import { Provider } from 'react-redux'
    import createStore from './store/index'
    
    const store = createStore()
    export default ({element}: {element: JSX.Element | JSX.Element[]}) => <Provider store={store}>{element}</Provider>

    SSR & Browser両方で読み込む

    あとは読み込みするようにしてやればOKです。gatsby-ssr.jsgatsby-broweser.jsに以下のコードを追加します。

    import wrapWithProvider from "./src/wrap-with-provider"
    export const wrapRootElement = wrapWithProvider

    Reactから実行する

    あとはReact SPAよろしくconnectしてdispatchしてやればOKです。

    import React from "react"
    import { Link } from "gatsby"
    import { connect } from 'react-redux'
    import * as first from '../modules/index'
    import {
      State
    } from '../store/index'
    
    const IndexPage = ({count, add, reduce, reset}: ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>) => (
      <Layout>
        <h1>Hi people {count}</h1>
        <p>Welcome to your new Gatsby site.</p>
        <p>Now go build something great.</p>
        <button onClick={() => add(1)}>add</button>
        <button onClick={() => reduce(1)}>reduce</button>
        <button onClick={() => reset()}>reset</button>
      </Layout>
    )
    
    const mapStateToProps = (state: State) => {
      return {
        count: state.first.count
      }
    }
    const mapDispatchToProps = (dispatch: Function) => ({
      add: (num: number) => dispatch(first.add(num)),
      reduce: (num: number) => dispatch(first.reduce(num)),
      reset: () => dispatch(first.reset()),
    })
    
    export default connect(mapStateToProps, mapDispatchToProps)(IndexPage)
    

    おわりに

    Providerの設定以外は特に普通のReact-Reduxと変わりありませんでした。

    ビルドした後でも問題なくReduxが動作してくれていますので、アプリとして実装したい場合にReduxが活躍してくれそうです。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark