かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

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

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

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

コンポーネント

まず、アラートを出すコンポーネントを準備します。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になってるので、アラートがつられて出ることはありません。

ソースコード全体

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