読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

TypeScript + React + Reduxでalertダイアログを出す

React TypeScript

こういう表示とデータを疎結合にしたときに問題になるのがダイアログ系。

頭をひねった感じこんなのでいけるんじゃないかっていう感じのが出来ました。

コンポーネント

まず、アラートを出すコンポーネントを準備します。UIは持たないrenderメソッドはnullを返すだけのコンポーネントです。特徴は、componentWillReceivePropsでプロパティにメッセージを受け取ったときにalertを出すようにしてるところです。

import * as React from 'react';

interface AlertProps extends React.Props<{}> {
    message: string;
}

export default class Alert extends React.Component<AlertProps, {}> {
    componentWillReceiveProps(nextProps: AlertProps) {
        if (nextProps.message) {
            alert(nextProps.message);
        }
    }

    render() {
        return null;
    };
}

アクション

こんなアクションのデータ形式を定義しておいて。

/**
 * Actionのインターフェース
 */
export interface Action<T> {
    type: string;
    payload?: T;
    error?: Error;
}

んで、Alert用のアクションのpayloadの型を定義しておいて

export default class AlertPayload {
    static get TYPE() {
        return 'ALERT';
    }

    constructor(public message: string) { }
}

アクションクリエイターでアクションを発生させます。このとき、メッセージセットのアクションとメッセージリセットのアクションを2つ連続して呼び出すところがポイントです。2つ連続して呼び出すためにredux-thunkを前提としたアクションになります。

import * as actions from './';
import * as Redux from 'redux';
import AlertPayload from './AlertPayload';

function setAlert(message: string): actions.Action<AlertPayload> {
    return {
        type: AlertPayload.TYPE,
        payload: new AlertPayload(message)
    };
}

export function alert(message: string) {
    return (dispatch: Redux.Dispatch) => {
        dispatch(setAlert(message));
        dispatch(setAlert(null));
    }; 
}

Reducer

Reducerは、素直にメッセージプロパティの書き換えを行います。

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

export function alert(state = new Alert(), action: actions.Action<any>) {
    switch (action.type) {
        case AlertPayload.TYPE:
            return assign({}, state, { message: (action.payload as AlertPayload).message } as Alert);
        default:
            return state;
    }
}

このAlertクラスは、コンポーネントのAlertクラスじゃなくて以下のようなデータの入れ物です。

export default class Alert {
    message: string;
}

繋ぐ

connectメソッドで繋いで完成。

import * as React from 'react';
import * as ReactRedux from 'react-redux';
import * as reducers from '../reducers';
import Alert from './Alert';

interface LayoutProps extends React.Props<{}> {
    userName: string;
    message: string;
}

class Layout extends React.Component<LayoutProps, {}> {
    render() {
        return (
            <div>
                <Alert message={this.props.message} />
                <h1 style={{display: 'inline-block'}}>Redux master detail application sample</h1>
                {this.props.userName ? <span>ユーザー名:{this.props.userName}</span> : null}
                <hr />
                {this.props.children}
            </div>
        );
    }
}

function select(state: reducers.AppState): LayoutProps {
    return {
        userName: state.auth.userName,
        message: state.alert.message
    };
}

export default ReactRedux.connect(select)(Layout);

stateに変更があるたびにAlertコンポーネントのプロパティに値がセットされてダイアログが出るという感じです。すぐにnullにリセットされるため、別のステートの変更があっても、そのときにはnullになってるので、アラートがつられて出ることはありません。

ソースコード全体

今作ってる別のサンプルプログラムに組み込んじゃってるのでもうちょい待ってください。