ReduxチュートリアルをTypeScriptでやってみた
Reactのチュートリアルが終わったので、今度はReduxのチュートリアル(Basics · Redux)をTypeScriptでやってみました。
これは非常に大変でした。
はじめに
Reactのチュートリアル(Tutorial: Intro To React - React)は、TypeScriptでまあまあ順調にやれたのですが、Reduxの方は激ムズでした。
これはちょっと初心者が手を出すべきものではないですね。
最初はTypeScriptなしでやり、ある程度慣れてきたらTypeScriptで書き直ししてみるのがいいと思います。
実はまだ解決できていない部分があるのですが、一応、自分がやったことを記録しておこうと思います。
基本的な修正
TypeScriptでやる場合、型の指定を行うことがメインになります。
ただし、Reduxの機能を使う場合、簡単に型を推測できない場合があります。
その場合、以下のようなサンプルコードが参考になりました:
また、Presentational Component がファンクションになっているので、React.Component から継承したクラスに直しました。
Actionのインターフェース定義
Actionについては、Reduxでベースのインターフェースが提供されているので、一応それを継承するようにしました。
import { Action } from 'redux'; export interface IAddTodoAction extends Action { type: string; id: number; text: string; }
また、Reducerでは任意のActionを受け取って処理を分岐することになるので、複数の異なるActionを1つの型に統合する必要があります:
// actions/index.ts export type TodoAction = IAddTodoAction | ISetVisibilityFilterAction | IToggleTodoAction; // reducers/todos.ts const todos = (state: State = [], action: TodoAction) => {...}
追記(2017-07-18):
統合した型を指定しなくても、Action を指定すれば大丈夫でした。
追記終了
また、中の処理でActionごとに処理を分岐する必要がありますが、Actionのインターフェースが異なる場合はキャストする必要があります:
const todos = (state: State = [], action: TodoAction) => { switch (action.type) { case 'ADD_TODO': const a1 = <IAddTodoAction> action; return [ ...state, { id: a1.id, text: a1.text, completed: false } ]; ...
dispatchの型
Containerの関数 mapDispatchToProps
の引数に dispatch
があるのですが、これはReduxの Dispatch
になります。
ただし、ジェネリックなので、Actionのインターフェースを指定する必要があります。
import { Dispatch } from 'redux'; const mapDispatchToProps = (dispatch: Dispatch<IToggleTodoAction>) => {...}
FilterLink containerについて
FilterLinkについては、結局、エラーを解消できませんでした。
const FilterLink = connect( mapStateToProps, mapDispatchToProps )(Link); // ここでエラー
エラーメッセージ:
[ts] Argument of type 'typeof Link' is not assignable to parameter of type 'Component<IProps & { active: boolean; } & { onClick: () => void; }>'. Type 'typeof Link' is not assignable to type 'StatelessComponent<IProps & { active: boolean; } & { onClick: () => void; }>'. Type 'typeof Link' provides no match for the signature '(props: IProps & { active: boolean; } & { onClick: () => void; } & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.
propsの食い違いのようですが、よくわかりませんでした。
他のContainerでは同様の実装で問題なかったので、FilterLinkの構造が影響しているのかもしれません。
後でわかったら更新します。
追記(2017-07-26)
ようやくわかりました。
Link の props のインターフェースに、FilterLink の ownProps のインターフェースの要素を含むようにしたら解消しました。
component は container の props を継承しないといけない、みたいな思想ですかね。ちょっと違和感がありますが。
追記終了
追記(2017-07-29)
ownProps は Component に指定された props ですね。
誤解していました。
追記終了
AddTodo container について
この部分に関しては、container内にJSXが書かれていて、ContainerとPresentational Componentがミックスされたような形になっています。
恐らく、そういったサンプルとして入れたのだと思いますが、React.Componentを継承したクラスに直すのが難しそうだったので、ファンクションのままにしました。
その場合に、パラメーターの { dispatch }
が問題になってきます。
これは、「分割代入」(Destructuring assignment) という記法で、パラメーターがオブジェクトで渡ってきて、その dispatch
という要素が dispatch
という変数に代入されることになります。
この型を定義する場合、{ dispatch }: IParam
のような形で指定することになりますが、dispatch
が関数なので IParam
の指定が複雑になります。
結局、ここは { dispatch }: any
で逃げてしまいました。(ワーニングが出ます。)
また、input変数の型もよくわからなかったので、let input: any;
で逃げました。
あとがき
Reactのチュートリアルの時は最終的にスッキリしたのですが、Reduxの方はすべてを解決することができず、TypeScriptでやっていく上で少し不安が残る結果となってしまいました。
今後、さらにReduxでの実装を経験して、いずれ解決していきたいと思います。