Closed1
Next.js + TypeScript + Redux
やりたいこと
- Next.js + TypeScript + Redux 環境構築
- Reduxを使用した簡単なState管理
Next.js + TypeScript + Redux 環境構築
Next.js環境作成
npx create-next-app next-redux
パッケージ追加
// Redux関連
yarn add redux react-redux next-redux-wrapper redux-thunk
// TypeScript関連
yarn add --dev typescript @types/react @types/node
.jsを.ts, .tsxへ変更
以下、公式からコピー
pages/posts/[id].js: Update to [id].tsx using this code
pages/index.js: Update to index.tsx using this code
pages/_app.js: Update to _app.tsx using this code
pages/api/hello.js: Update to hello.ts using this code
tsconfig.json, next-end.d.ts 作成
yarn dev を実行すると、tsconfig.jsonとnext-end.d.tsが自動で作成される。
初期ファイルの型付
面倒だからボイラープレート探そう。
以下でindex.tsx, _app.tsxのところを参考にする。
Reduxを使用した簡単なState管理
初期準備
ディレクトリ作成(最適解がわからない)
↓の方のを参考にしてみる。(re-ducsパターンという構成の様)
duck/
|-myButton
|-countReducer.ts
./store.ts
reducer
export interface CountState {
counter: number
}
const initialState = {
counter: 0,
}
type Action = {
type: 'count/increment'
payload: number
}
export const countReducer = (state: CountState = initialState, action: Action) => {
switch (action.type) {
case 'count/increment':
return { counter: state.counter + action.payload }
default:
return state
}
}
store
import { countReducer } from './duck/MyButton/countReducer'
import { createStore } from 'redux'
export const store = createStore(countReducer)
app.tsxでstoreを読み込む
import React, { FC } from 'react'
import { Provider } from 'react-redux'
import { store } from '@/store'
const MyApp: FC = ({ Component, pageProps }) => {
return (
<React.Fragment>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</React.Fragment>
)
}
export default MyApp
ここまででグローバルStateの使用準備完了
各コンポーネントでのグローバルstateを参照、dispatch
useSelector
useDispatch
stateのインクリメントbuttonコンポーネント作成
import React, { FC } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { CountState } from '@/duck/myButton/countReducer'
const MyButton: FC = () => {
const mycounter = useSelector<CountState, CountState['counter']>((state) => state.counter)
const dispatch = useDispatch()
const handleCountUpBtn = (value: number) => {
dispatch({ type: 'count/increment', payload: value })
}
return (
<div>
<button
onClick={() => {
handleCountUpBtn(1)
}}
>
[+] (myButton)
</button>
<p>※debug counter:{mycounter}</p>
</div>
)
}
export default MyButton
index.tsx側でボタン作成 + 上のコンポーネントを読み込み、どちらのボタンを押してもStoreが更新されるように
import React, { FC } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { CountState } from '@/duck/myButton/countReducer'
import MyButton from '@/components/MyButton'
const Home: FC = () => {
const counter = useSelector<CountState, CountState['counter']>((state) => state.counter)
const dispatch = useDispatch()
const handleCountUpBtn = (value: number) => {
dispatch({ type: 'count/increment', payload: value })
}
return (
<div>
<p>counter: {counter}</p>
<button
onClick={() => {
handleCountUpBtn(1)
}}
>
[+] (index.tsx)
</button>
<MyButton />
</div>
)
}
export default Home
ここまでで画面上でreduxを用いた挙動が確認できる
Actionの分離
./page/index.tsx, ./components/MyButton.tsxでdispatch関数に直接埋め込んでいたAcitonを分離する。
duck以下にacitonファイルを作成後、各ファイルを修正。
export type Action = {
type: 'count/increment'
payload: number
}
export const countIncrement = (value: Action): Action => ({
type: 'count/increment',
payload: value,
})
code:pages/index.tsx
// ... 省略
import { countIncrement } from '@/duck/myButton/action'
const Home: FC = () => {
// ... 省略
const handleCountUpBtn = (value: number) => {
dispatch(countIncrement(value))
}
// ... 省略
}
export default Home
// ... 省略
import { countIncrement } from '@/duck/myButton/action'
const MyButton: FC = () => {
// ... 省略
const handleCountUpBtn = (value: number) => {
dispatch(countIncrement(value))
}
// ... 省略
}
export default MyButton
参考
Next.js公式 TypeScript導入
Next.jsでTypeScriptの環境を構築する
TypeScriptのプロジェクトにESLintとPrettierを導入する方法(+VSCodeでの設定)
boiler plate
Reduxでのディレクトリ構成3パターンに見る「分割」と「分散」
nextjs with typescript:40 Reduxの使い方
このスクラップは2022/09/15にクローズされました