Quantcast
Channel: かずきのBlog@hatena
Viewing all articles
Browse latest Browse all 1387

Reduxってどうなってるんだってばよ

$
0
0

Reduxが難しい。理解できたらいいんだろうけど。 TypeScriptで開発するとき、何をどの順番で作っていけばすっきりするのかわからない。

ということで整理もかねて徒然なるままにBlogに向かいて書いてみたいと思います。

Actions

まず、何はともあれアクションです。Actionは、せっかくのTypeScriptなんでインターフェースを定義しておきたいところですね。

exportinterface Action<TPayload> {
    type: string;
    payload?: TPayload;
    error?: Error;
}

Actionを識別するための定数ってどこに切るよ?actionTypes.tsというファイルを作ってその中でexport constをするのがセオリーっぽい気もするけど、TypeScriptだと、後々Actionのpayloadの型を定義するから、そこに含めてもいいかもしれない?こんな感じに。

exportdefaultclass SetAnswerPayload {static TYPE = 'SET_ANSWER_ACTION';

    constructor(public answer: number) {}}

constじゃないのが嫌な感じだけど。まぁ書き換えないっしょ。

Action Creatorsどうしましょう?っていうかこれは普通に関数をexportするだけですよね?

import * as actions from './index';
import SetAnswerAction from './SetAnswerPayload';
import assign = require('object-assign');
import * as Redux from 'redux';

function setAnswer(answer: number): actions.Action<SetAnswerAction> {return{
        type: SetAnswerAction.TYPE,
        payload: new SetAnswerAction(answer)
    };
}exportfunction add(x: number, y: number) {return (dispatch: Redux.Dispatch) => {
        fetch('api/Calc?x=' + x + '&y=' + y)
            .then(x => {if (x.status !== 200) {thrownew Error();
                }return x.json();
            }).then((x: number) => {
                dispatch(setAnswer(x));
            }).catch(_ => {});
    };
}

んで、実践的な奴だとWebAPI呼び出すからこんな感じにredux-thunk使いますよね。

んで、このAction Creatorsを1ファイルにどれくらいまとめてしまうのか悩ましい。OOPでいうところの1クラスぶんを1ファイルにまとめてしまうくらいの感覚でいいのだろうか。

アプリのモデル

JavaScriptだとreducerを書いていけばいいんだけど、そこで使うデータモデルの設計しないといけないですよね。TypeScriptだとクラスの定義。どんなモデルが必要なのかさくっと定義するといいんでしょう。

exportdefaultclass Calc {
    answer: number;
}

Reducers

んで、アクションをディスパッチするReducersですよね。このReducersで嫌いなswitch文の嵐。アクションで1ファイルにまとめたのと同じぶんだけ1ファイルにまとめたReducersを作ればいいのかしら。

import Calc from '../models/Calc';
import * as actions from '../actions';
import SetAnswerPayload from '../actions/SetAnswerPayload';
import assign = require('object-assign');

function setAnswer(calc: Calc, p: SetAnswerPayload) {return assign({}, calc, {
        answer: p.answer
    } as Calc);
}exportdefaultfunction calcApp(calc = new Calc(), a: actions.Action<any>): Calc {switch (a.type) {case SetAnswerPayload.TYPE:
            if (a.error) {// TODOreturn calc;
            }return setAnswer(calc, a.payload as SetAnswerPayload);
        default:
            return calc;
    }}

アプリのStoreの型定義

これをよく忘れる。(というか今作りながらやってて忘れてた)

reducerをまとめ上げるところで一緒に定義してやるといい感じだと思う。

import * as Redux from 'redux';
import CalcReducers from './CalcReducers';
import Calc from '../models/Calc';

exportinterface AppState {
    calc: Calc;
}exportconst appReducer = Redux.combineReducers({
    calc: CalcReducers
});

コンポーネント

そしたら、コンポーネントを作ればいい? このコンポーネントもconnectしないといけないというのを忘れがち。

import * as React from 'react';
import * as Redux from 'redux';
import * as ReactRedux from 'react-redux';
import Calc from '../models/Calc';
import * as calcActionCreators from '../actions/calcActionCreators';
import * as reducers from '../reducers';

interface IndexPageProps extends React.Props<{}> {
    dispatch?: Redux.Dispatch;
    calc?: Calc;
}interface IndexPageState {}class IndexPage extends React.Component<IndexPageProps, IndexPageState> {private handleSubmit(e: React.SyntheticEvent) {
        e.preventDefault();
        var x = parseInt((this.refs['x'] as HTMLInputElement).value);
        var y = parseInt((this.refs['y'] as HTMLInputElement).value);
        this.props.dispatch(calcActionCreators.add(x, y));
    }

    render() {return (
            <form onSubmit={this.handleSubmit.bind(this)}>
                <input type='text' ref='x' />
                &nbsp;
                +
                &nbsp;
                <input type='text' ref='y' />
                &nbsp;
                <input type='submit' value='=' />
                &nbsp;
                <span>{this.props.calc.answer}</span>
            </form>
        );
    }}function select(state: reducers.AppState): IndexPageProps {return{
        calc: state.calc
    };
}exportdefault ReactRedux.connect(select)(IndexPage);

まとめ上げよう

最後に、appクラス。 storeを作ってProviderに渡す。

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as Redux from 'redux';
import{Provider} from 'react-redux';
import IndexPage from './components/IndexPage';
import * as reducers from './reducers';
import thunk = require('redux-thunk');

class App extends React.Component<{}, {}> {
    render() {return (
            <div>
                <h1>Calc app</h1>
                <hr />
                <IndexPage />
            </div>
        );
    }}const createStoreWithMiddleware = Redux.applyMiddleware(thunk as any)(Redux.createStore);
const store = createStoreWithMiddleware(reducers.appReducer);

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('content'));

実行すると動いた。

まとめると

Actions → Action Creators → Models → Reducers → Store → Componentsの流れで一回作って、次からはActions → Action Creators → Model(必要に応じて) → Reducers → Store(必要に応じて) → Componentsの流れで作ればいいのか。メモメモ


Viewing all articles
Browse latest Browse all 1387

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>