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.
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.js
とgatsby-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が活躍してくれそうです。