[Gatsby x TypeScript]Reduxを使った言語設定の表示切り替え
Gatsby x Wordpress でプロジェクト作成中、言語切り替えの値をstore管理したくてやり方を調べていた。途中ChatGPTなんかにも助けてもらいつつ備忘録をまとめた。(コピペ説明部分ありです)
免責:当方React もGatsbyもTypeScriptも初学者です。
ほか参考
Reac初心者でも読めば必ずわかるReactのRedux講座
ts公式 : React Redux TypeScript Quick Start
Getting started
初期プロジェクトならViteやNext.js向けにテンプレートがあるらしいが、既存アプリなので俺は黙ってnpm install.。react-reduxはReactとReduxを連携させるためのもの、@reduxjs/toolkitはReduxのボイラープレートを減らすためのツールセット
npm install @reduxjs/toolkit react-redux
ここでTsユーザーに朗報なのだが、React-Redux v8はTsで書かれているため、全ては型定義済みとのこと。
React-Redux v8 is written in TypeScript, so all types are automatically included.
この間エラー出まくったのでsassのバージョン上げたりgatsbyバージョン上げたり依存関係修復してました。Gatsby 5.13.12-14の3つ(どれも安定版)でビルドし直していたのですが、どれもreact-server-dom-webpackが特定の実験的なバージョンのReactを要求しているらしく("0.0.0-experimental-c8...")warningを全て解消するのは(現時点では)ちょっよ骨が折れるなあ、ということで断念。23 warningでヒヤヒヤしながらコーディングを続けようと思います。エロい人がいたら教えてください。
npm ls react // こっちで問題出てた
npm ls gatsby
Reduxのストアを設定
npm install @reduxjs/toolkit react-redux
できたので気を取り直してstore設定。
ここでは、@reduxjs/toolkitのconfigureStore
を使用。これにより、middlewareやdevtoolsの設定が簡単になるらしい。
toolkitについて。前置き長いので23:45頃から
YouTube: Let’s Learn Modern Redux! (with Mark Erikson) — Learn With Jason
Actionとreducerの作成
Redux Toolkit の createSlice
関数を使い、言語設定管理のための
- reducer
- action
を定義。
1. 初期状態の定義 (Initial State)
src/store/
ディレクトリ作成。
-
LanguageState
インターフェース: ここでは、状態の型を定義しています。language
プロパティは、文字列リテラル 'En' または 'Ja' のみを受け入れるようになっています。これにより、言語設定がこれらの値に限定されることを型システムが保証します。 -
initialState
: この変数は、リデューサーの初期状態を設定します。アプリケーションの言語設定のデフォルトは 'En'(英語)です。
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
// Define a type for the slice state
interface LanguageState {
language: 'en' | 'ja'
}
// Define the initial state in reducer
const initialState: LanguageState = {
language: 'en', // default lang
}
2. Slice の作成
-
createSlice
関数: Redux Toolkit によって提供されるこの関数は、リデューサーと関連するアクションを定義するためのもの。 -
name
: スライスの名前を指定。この名前は、生成されるアクションタイプにプレフィックスとして使用される。 -
initialState
: 上で定義した初期状態をこのスライスに割り当て。 -
reducers
オブジェクト: アクションタイプとそれを処理するリデューサーロジックのマッピング。この例では、setLanguage
アクションが定義されている。-
setLanguage
リデューサー:state
とaction
を引数に取り、state.language
をaction.payload
で更新。PayloadAction<'En' | 'Ja'>
は、このアクションのペイロードが 'En' または 'Ja' であることを保証。
-
const LanguageSlice = createSlice({
// name: name of the slice. Used as a prefix for action types
name: 'language',
// initialState: 上で定義した初期状態をこのスライスに割り当て
initialState,
// reducers: アクションタイプとそれを処理するリデューサーロジックのマッピング
reducers: {
// state と action を引数に取り、state.language を action.payload で更新
setLanguage: (state, action: PayloadAction<'en' | 'ja'>) => {
state.language = action.payload
},
},
})
3. アクションとリデューサーのエクスポート
-
languageSlice.actions
:createSlice
によって自動的に生成されるアクションクリエーター。setLanguage
アクションクリエーターをエクスポートしている。これでコンポーネントからこのアクションを簡単にディスパッチできる。 -
languageSlice.reducer
: スライスに対応するリデューサー。このリデューサーは、スライスの状態を更新するためのロジックを含んでいる。このリデューサーをストアの設定時に組み込むことで、対応するアクションがディスパッチされたときに状態更新が行われる。
export const { setLanguage } = LanguageSlice.actions;
export default LanguageSlice.reducer;
全コード
// src/state/languageSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
// Define a type for the slice state
interface LanguageState {
language: 'en' | 'ja'
}
// Define the initial state in reducer
const initialState: LanguageState = {
language: 'en', // default lang
}
// Define Slice
// createSlice can define reducers and action creators together
const LanguageSlice = createSlice({
// name: name of the slice. Used as a prefix for action types
name: 'language',
// initialState: 上で定義した初期状態をこのスライスに割り当て
initialState,
// reducers: アクションタイプとそれを処理するリデューサーロジックのマッピング
reducers: {
// state と action を引数に取り、state.language を action.payload で更新
setLanguage: (state, action: PayloadAction<'en' | 'ja'>) => {
state.language = action.payload
},
},
})
// languageSlice.actions: createSlice によって自動的に生成されるアクションクリエーター
// setLanguage アクションクリエーターをエクスポート
export const { setLanguage } = LanguageSlice.actions;
// languageSlice.reducer: スライスに対応するリデューサー
// リデューサーをストアの設定時に組み込むことで、対応するアクションがディスパッチされたときに状態更新が行われる
export default LanguageSlice.reducer;
Redux Toolkit の configureStore を使用してアプリケーションのストアを設定
configureStore
関数を使用し Redux ストアを作成.
アプリケーション全体で利用するための設定を行う。
- インポートと依存関係
-
configureStore
: Redux Toolkit からconfigureStore
関数をインポート。
この関数はストアを作成し、Redux DevTools と thunk ミドルウェアなどの一般的なミドルウェアを自動的に設定。 -
languageReducer
:languageSlice.ts
で定義されたリデューサーをインポート。これは言語設定の状態を管理するためのリデューサー。
import { configureStore } from '@reduxjs/toolkit';
import languageReducer from './languageSlice';
2. ストアの作成
-
configureStore
関数を使用しストアを作成。この関数にはオプションを含むオブジェクトを渡す。 -
reducer
: このオプションは、アプリケーションの各状態を管理するリデューサーをマッピングするオブジェクト。
ここではlanguage
というキーにlanguageReducer
を割り当て。これにより、language
の状態に対するすべての操作がlanguageReducer
によって処理される。
const store = configureStore({
reducer: {
language: languageReducer,
},
});
3. 型定義のエクスポート
-
RootState
: ストアの全体の状態の型を定義。
store.getState
の戻り値の型を利用して、ストアの状態の型を取得しています。これにより、コンポーネントや他の部分で状態を参照する際に型安全が保証される -
AppDispatch
: ストアの dispatch 関数の型を定義。これは、アクションをディスパッチする際に使用され、型安全なディスパッチ操作を可能にする。 - ストアのエクスポート
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
コード全文
// src/store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import languageReducer from './languageSlice';
const store = configureStore({
reducer: {
language: languageReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
React 全体に適用
// gatsby-browser.tsx
import React from 'react'
import { Provider } from 'react-redux'
import store from './src/store/store'
export const wrapRootElement = ({ element }: { element: React.ReactNode }) => (
<Provider store={store}>{element}</Provider>
)
コンポーネントで使用
react-redux の useSelector
と useDispatch
フックを使い、Redux ストアの状態を読み取り、アクションをディスパッチする。
1. 必要なモジュールのインポート
// header.tsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from '../store/store';
import { setLanguage } from '../store/languageSlice';
-
useSelector
,useDispatch
: Redux フックをインポート。useSelector
は Redux ストアの状態を読み取るため、useDispatch
はアクションをディスパッチするために使用。 -
RootState
,AppDispatch
: ストアの型定義をインポート。これにより、TypeScript がストアの状態とディスパッチ関数の型を検証できる。 -
setLanguage
: 言語設定を切り替えるためのアクションクリエーター
2. コンポーネントの定義
// header.tsx
const LanguageSwitcher: React.FC = () => {
const language = useSelector((state: RootState) => state.language.language);
const dispatch = useDispatch<AppDispatch>();
// せっかくだからクラスもつける
const langBtnClassEn = `lang_btn_en __${language === 'en' ? 'en' : 'ja'}`
const langBtnClassJa = `lang_btn_ja __${language === 'en' ? 'en' : 'ja'}`
return (
<>
{// 省略}
<div className="hidden lg:flex lg:flex-1 lg:justify-end">
<div className="text-sm font-semibold leading-6 text-gray-900">
<button className={langBtnClassEn} onClick={() => dispatch(setLanguage('en'))}>
EN
</button>{' '}
/{' '}
<button className={langBtnClassJa} onClick={() => dispatch(setLanguage('ja'))}>
JA
</button>
</div>
</div>
</>
);
};
-
コンポーネントの構造:
LanguageSwitcher
は関数コンポーネントとして定義されており、React の Functional Component (FC) 型を使用。 -
言語の読み取り:
useSelector
フックを使用して、Redux ストアから現在の言語設定 (language
) を読み取り。RootState
を使って、適切な型情報とともにストアの状態を参照する。 -
アクションのディスパッチ:
useDispatch
フックを用いて Redux ストアのdispatch
関数を取得する。ボタンがクリックされると、setLanguage
アクションがディスパッチされ、言語設定が更新される。
スタイルはこんな感じ。style.scssでインポートしてる。
// header/style.scss
.lang_btn_en,
.lang_btn_ja {
transition: color 0.5s ease;
}
.lang_btn_en {
&.__en {
color: #cfa254;
}
&.__ja {
color: #444;
}
}
.lang_btn_ja {
&.__ja {
color: #cfa254;
}
&.__en {
color: #444;
}
}
3. JSX のレンダリング
<div className="hidden lg:flex lg:flex-1 lg:justify-end">
<div className="text-sm font-semibold leading-6 text-gray-900">
<button className={langBtnClassEn} onClick={() => dispatch(setLanguage('en'))}>
EN
</button>{' '}
/{' '}
<button className={langBtnClassJa} onClick={() => dispatch(setLanguage('ja'))}>
JA
</button>
</div>
</div>
-
表示: ボタンに
onClick
イベントハンドラを設定。クリックで指定言語に切り替える(予定)。 -
アクションのトリガー: ボタンがクリックされたとき、
dispatch(setLanguage('En'))
やdispatch(setLanguage('Ja'))
が呼び出され、言語設定の状態が更新される。 -
<p>{language}<p/>
とかどっかに入れると状態見れる
4. コンポーネントのエクスポート
export default LanguageSwitcher;
ページでの表示切り替え
languageの値に応じて表示変更する。例として、プロフィールを表示するAboutページで。
インポート
// about.tsx
import React from 'react'
import { useSelector } from 'react-redux'
import { RootState } from '../store/store'
マークダウンの表示にはreact-markdow
を使用。
npm i react-markdown@8.0.6
インポート
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
- useSelector フックを使って Redux ストアから現在の言語設定 language を取得。
- 取得した language に基づいて myIntroduction 配列から対応するプロフィールを検索。
検索に失敗した場合のデフォルトとして myIntroduction[0] (通常は英語) を設定。 - 選択された profile の name と description を表示.
マークダウン実装
- オブジェクトに入れたマークダウンを
<Markdown remarkPlugins={[remarkGfm]}>{profile.description}</Markdown>
として表示するだけ。 - オブジェクトに入れるmarkdownはインデント入れないよう注意。(ここでハマった!)
// about.tsx
const AboutPage = ({ data, location }: PageProps<GatsbyTypes.Query>) => {
const language = useSelector((state: RootState) => state.language.language)
const myIntroduction = [
{
lang: 'en',
name: 'test osterone',
description: `
## Front-End Developer
---
### 🌐 About Me
Aspiring front-end developer skilled in HTML, CSS, JavaScript, and React.
### 🚀 Projects
- **Portfolio Website:** Built with React. [Visit Site](#)
### 📞 Contact
- **Email:** test.sterone@example.com
- **LinkedIn:** [linkedin.com/in/testosterone](#)
`,
},
{
lang: 'ja',
name: 'テスト ステロン',
description: `
## フロントエンド開発者
---
### 🌐 自己紹介
HTML、CSS、JavaScript、Reactを得意とするフロントエンド開発者。
### 🚀 プロジェクト
- **ポートフォリオウェブサイト:** Reactで構築。[サイトを見る](#)
### 📞 連絡先
- **メール:** test.sterone@example.com
- **LinkedIn:** [linkedin.com/in/testosterone](#)
`,
},
]
const profile = myIntroduction.find(p => p.lang === language) || myIntroduction[0]
return (
<>
<Layout location={{ pathname: '/about' }} title="About">
<Seo title="About" />
<h1>About Me</h1>
<h2>{profile.name}</h2>
<article>
<Markdown remarkPlugins={[remarkGfm]}>{profile.description}</Markdown>
</article>
</Layout>
</>
)
}
export default AboutPage
できた〜!
今度はmdファイルを別ファイルとして読み込む設定をしたい。
Discussion