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のところを参考にする。
https://github.com/sanjaytwisk/nextjs-ts

Reduxを使用した簡単なState管理

初期準備
ディレクトリ作成(最適解がわからない)

↓の方のを参考にしてみる。(re-ducsパターンという構成の様)
https://note.com/fz5050/n/n0ec68f6b5729

 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
https://react-redux.js.org/api/hooks#useselector

useDispatch
https://react-redux.js.org/api/hooks#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導入
https://nextjs.org/learn/excel/typescript

Next.jsでTypeScriptの環境を構築する
https://tech.playground.style/javascript/next-js-typescript/

TypeScriptのプロジェクトにESLintとPrettierを導入する方法(+VSCodeでの設定)
https://qiita.com/yuma-ito-bd/items/cca7490fd7e300bbf169

boiler plate
https://github.com/sanjaytwisk/nextjs-ts

Reduxでのディレクトリ構成3パターンに見る「分割」と「分散」
https://superhahnah.com/redux-directory-petterns/

nextjs with typescript:40 Reduxの使い方
https://note.com/fz5050/n/n0ec68f6b5729

このスクラップは2022/09/15にクローズされました