何をやるのか
https://react-redux.js.org/tutorials/typescript-quick-start
React Redux Tutorial
カウンターアプリに TS を導入し、
呼び出し元で意識することを少なくするために
- グローバルステートを取ってくる useAppSelector
- グローバルステートの変更メソッドを呼び出す useAppDispatch
これらを hooks に定義する。
前回作ったプロジェクトに TS を追加しようとして失敗
https://makeanapplike.com/yarn-create-react-app-typescript-redux-npm/
npm i typescript @types/node @types/react @types/react-dom @types/jest
@types
の node, react, react-dom, jest, のライブラリを追加
index.js を index.tsx に変更
サーバーを再起動
これだけで TS が動かせるようになるらしい。
実際に動かしたらエラーは出なかった。
しかし、その後 App.js, store.js, Counter.js を ts に書き直すと
Can't resolve './app/store' in '/Users/kaede0902/code/redux/src'
このように読み込めなくなってしまった。
https://iwb.jp/webpack-config-js-module-not-found-error-cant-resolve/
この記事を読むと、Webpack を導入しないと index 以外の TS は読み込めないらしい。
CRA with TS で作って前回の記事のところまでアプリを作り直す
ライブラリの追加だけでは TS ファイルを読み込めなかったので、
CRA の時点で TS を組み込んで作成し直す。
npx create-react-app ts-redux --template typescript
npm i react-redux @reduxjs/toolkit
CRA を TS で作って、
react-redux @reduxjs/toolkit のライブラリを入れる
import { configureStore } from '@reduxjs/toolkit'
export const store =configureStore({
reducer: {},
})
export default store
これで app/store に store ファイルを TS で作って
import store from './app/store'
import { Provider } from 'react-redux'
<Provider store={store}>
<App />
</Provider>
index.tsx で読み込んで動作確認すると
TS で書いたストアファイルがちゃんと読み込まれているのを確認できた。
https://dev.to/kaede_io/react-redux-part-1-react-redux-nodao-ru-5o1
そして、前回の記事通りに Counter アプリを ts や tsx の拡張子で作成する
app/store のストアファイルでの configureStore の export を止め、 RootState と AppDispatch の型を定義して export する
store という変数で reducer を configureStore でまとめる。
countReducer は後ほど作る。
const store = configureStore({
reducer: {
counter: counterReducer,
},
})
export default store
このように、単に reducer をラップして export していた
configureStore を変更する。そして default で export
これで {} をつけなくても import できる
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
そしてその変数 store から
getState するときの type を RootState
dispatch するときの type を AppDispatch
として export する。
hooks を作る
ストアファイルに作った RootState と AppDispatch は
そのまま描画コンポーネントで使う訳ではない。
Custom Hooks を定義し、ここでラップされる。
src/app/ に hooks.ts を作成し
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
react-redux の npm ライブラリから
普段の useDispatch, useSelector の他に
TypedUseSelectorHook もインポート
さらに先ほど作った store ファイルから RootState と AppDispatch をインポート
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
AppDispatch の型を使って useDispach する useAppDispach
TypedUseSelectorHook と RootState の型でチェックして useSelector を入れる? useAppSelector
これらを hooks から export
そして今まで使っていた useDispatch と useSelector をこれらに変更する。
features/counter/CounterSlice でグローバルステート counter 初期値を数値型で定義し直す
次は Slice ファイルに TS を導入する。
interface CounterState {
value: number
}
const initialState: CounterState = {
value: 0,
}
値が数値型になる interafce の CounterState を定義
CounterState を使って 初期値 0 で initialState を定義
export const counterSlice = createSlice({
name: 'counter',
initialState,
その initialState を counter を作る時に初期値として使った。
CounterSlice で reducers の incrementByAmount がちゃんと action.payload の形だけ受け取れるようにする
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
reducers: {
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
action.payload に余計なものが入らないようにした。
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
})
counterSlice コンポーネントの全体はこうなる。
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
actions や reducers を export するのも忘れずに。
Counter コンポーネントで hooks の useAppSelector と useAppDispatch を呼び出す
import { useAppSelector, useAppDispatch } from '../../app/hooks'
const count = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
useAppSelector, useAppDispatch を import して
count, dispatch としてコンポーネント内部で定義して
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
ボタンに組み込んで使う。
動作確認
動作確認ができた。
まとめ
JS で書いていた Redux の Counter アプリを
TS でモダンに書き換えるためには
モジュールとして export していた store を変数にして
それを使って RootState と AppDispatch の型を作成
hooks というファイルでそれらと reduxtoolkit の
TypedUseSelectorHook, useDispatch, useSelector
これらを合わせて
useAppSelector, useAppDispatch
というものにまとめて
counterSlice の slice ファイルで、初期値に型をつけ
payload を使う reducer にも型をつけ
Counter コンポーネントで
hooks の useAppSelector, useAppDispatchを使い、
useAppSelector で グローバルステートの counter を呼び出し
useAppDispatch で counterSlice の decrement, increment を呼び出し
それぞれ表示部分とボタンの関数部分に組み込む。
今後
https://react-redux.js.org/tutorials/connect
connect API を使って
Todo アプリを作るチュートリアルに進む。
Top comments (0)