[chatMain]みなさんこんにちは!今回はTypescriptとReduxでカウンタアプリを作ってみようと思います![/chatMain]
[chatReply]おお!いつもは商品レビューとか書いてるのに、今日はエンジニアっぽいこと書くんだな![/chatReply]
[chatMain]もちろん!そもそも私はエンジニアですからね!たまにはエンジニアっぽいことも書かないとなーと思って筆を取った次第です![/chatMain]
今回の成果物
ではまず最初に今回のアプリの挙動を見ていきたい。
各ソースコード
今回のアプリで必要なライブラリは下記の通り!
- react-redux
- redux
- @types/react-redux
- @types/redux
最低限これらがあれば動作するようになっている。
初期状態では、カウントは0になっている。
incrementボタンを押すと1カウントがプラスされる。
decrementを押すと1カウントがマイナスされる。
超絶シンプルなカウンタアプリになっている。
Action
まずEnumで今回のアプリで実装するActionを定義する。
incrementとdecrementだけだから、そのまま定義。
次に、InterfaceでActionの型を定義する。
今回はreduxライブラリのActionを継承したInterfaceを作っているが、Actionはtypeを持っているらしい。
最後にDispacherで使うためのAmountを実装する。
import {Action} from 'redux' export enum ActionNames{ Increment = 'inc', Decrement = 'dec' } export interface IIncrement extends Action{ type : ActionNames.Increment } export interface IDecrement extends Action { type : ActionNames.Decrement; } // 名前は何でもいい。IncrementでもIncでも。なんでも。 // 戻り値は上で定義したInterfaceを返すようにする。 // いわばActionCreatorといわれるやつ。 export const IncrementAmount = () : IIncrement => ({type : ActionNames.Increment}); export const DecrementAmount = () : IDecrement => ({type : ActionNames.Decrement}); export type ActionType = IIncrement | IDecrement;
Reducer
続いてReducerではactionに対する実装を書く。
また同時に画面でどのようなstateを持っているのかも書く。
今回の場合は、数字を増やしたりするだけなので、num一つだけ持っている。
またreducerの中のreturnで結構ミスをして、stateが変わらない!ってケースが多いから注意。
import {ActionNames, ActionType} from "../Actions/CounterAction"; export interface IState { num : number } const initialState : IState = {num: 0}; const CounterReducer = (state : IState = initialState , action : ActionType): IState => { switch (action.type) { case ActionNames.Increment: // 値が変わらない場合などはここでミスってる場合が多いから注意。 return {num : state.num + 1}; case ActionNames.Decrement: return {num : state.num - 1}; default: return state; } }; export default CounterReducer;
Store
storeはよくわからん。
今時点では、私も書き方を理解してないため、呪文のようにただただ覚えて書いている。
ただ、後ほど出てくるIndex.tsxのProviderコンポーネントのstoreに渡すためのstoreのオブジェクトを作るとは知っている。
私は今の時点では、こんなもんかーって感じ。
import {combineReducers,createStore,Action} from 'redux' import CounterReducer, {IState} from "../Reducers/CounterReducer"; import {ActionType} from "../Actions/CounterAction"; const rootReducer = combineReducers( {CounterReducer} ); export default createStore(rootReducer); export type ReduxState = { // ここでReducerの名前を正しく書かないとstateがバグる CounterReducer : IState }; export type ReduxAction = ActionType | Action;
Container
コンポーネントの中で実装する関数を定義する?場所的な感じだと思う。
ここで定義した関数をコンポーネントで使えるようになる。
また、connectの中でいろいろしてるが、これも今は呪文的な感じで書いている。
コンポーネント内で使うpropsと紐づけるためにいろいろ書いている。と理解している。
import {ReduxAction, ReduxState} from "../Store/store"; import {DecrementAmount, IncrementAmount} from "../Actions/CounterAction"; import {connect} from "react-redux"; import {Dispatch} from "react"; import Counter from "./Counter"; export class ActionDispather{ constructor(private dispatch: (action : ReduxAction) => void) { } public Increment() { this.dispatch(IncrementAmount()) } public Decrement(){ this.dispatch(DecrementAmount()) } } export default connect( (state : ReduxState) => { return {value : state.CounterReducer} }, (dispatch : Dispatch<ReduxAction>) => ({actions : new ActionDispather(dispatch)}) )(Counter)
ちなみに下記の箇所は書き方がいっぱいある。
export class ActionDispatcher { constructor(private dispatch: (action: ReduxAction) => void) {}
パターン1
export class ActionDispatcher { dispatch : (action: ReduxAction) => void constructor(dispatch: (action: ReduxAction) => void) { this.dispatch = dispatch; }
パターン2
type dispatchType = (action: ReduxAction) => void export class ActionDispatcher { dispatch : dispatchType; constructor(dispatch: dispatchType) { this.dispatch = dispatch; }
パターン3
type dispatchType = (action: ReduxAction) => void export class ActionDispatcher { constructor(private dispatch: dispatchType) { }
なにをしているのか?
これは結局のところ型付けである。
actionという名前で、ReduxAction型を受け取り、戻り値がvoid型の関数という型を指定しているのである。
なので、この関数の型をtypeエイリアスに入れてしまえば結構楽になる。
Component
続いて、コンポーネント。
好きかって書いていいと思う。
が、Propsは上記のContainerのconnectで書いた型を受け付けるようInterfaceで宣言にしないといけない。
もちろんコンポーネントが上記で指定した型のpropsを受け取れるようにする。
import React from "react"; import {IState} from "../Reducers/CounterReducer"; import {ActionDispather} from "./Container"; interface Props { value : IState; actions : ActionDispather } class Counter extends React.Component<Props, {}>{ render() { return ( <div> <p>{this.props.value.num}</p> <button type={"button"} onClick={() => this.props.actions.Increment()}>Increment</button> <button type={"button"} onClick={() => this.props.actions.Decrement()}>Increment</button> </div> ) } } export default Counter
Index.tsx
Indexはシンプル。
Providerで囲んで、さっき作ったstoreを渡すだけ。
どういう仕組みかは詳しく知らんが、これで動く。
import React from 'react'; import ReactDOM from 'react-dom'; import * as serviceWorker from './serviceWorker'; import {Provider} from "react-redux"; import store from "./Store/store"; import Counter from "./Component/Container"; ReactDOM.render( <Provider store={store}> <Counter /> </Provider>, document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
Gitに上げてます!
このコードはGitのリポジトリに上げています。
私はTypescript×Reduxを学ぶときに、まずは動くアプリを目標にしました。
なぜならとりあえず動けば、いろいろなステータスを変えればどうなっているかわかるから。
ただネット上にはコンパイルエラーだの何かのエラーで動くコードがあるというのがまれで、結構な時間を消費した。
だから今回のアプリは実際に動く形としてGitに上げている。
- https://github.com/adaman3568/Typescriot-CounterApp/network/dependencies.git
以上。
コメント