WebDesign Dackel

Reduxで定期的なアクションを実行する簡単なサンプルを作ってみた

Reduxで定期的なアクションを実行する簡単なサンプルを作ってみた

Hatena0
Google+0
Pocket0
Feedly0

個人的に作ってみたいサイトがあって、どうせならモダンな感じで書きたいなぁということで、少しずつReduxを触ってみています。

Reduxは初めて使いますが、Reactは以前、CoffeeScriptで少し書いて以来あまり触れてなかったので、Reactの復習も兼ねて定期的なアクションを実行するような簡単なサンプルを作ってみました。

以前にReactを触ってみた記事は以下。

成果物

スクリーンショット

動作サンプル

一秒毎にアクションを実行して、以下の処理を行うようなサンプルとなります。

  • 現在時刻を表示
  • 現在時刻からカラーコードを生成して、画面の背景色に設定

ご存知の方もいると思いますが、What colour is it?というサイトみたいな内容です!


今回のファイルは以下のリポジトリに一通りあがっています。

tsuyoshiwada/redux-samples/clock

Reduxにおけるコンポーネントの種類について

以下の参考サイトに書かれている内容がとっても分かりやすかったです。

Reduxexamplesをみてみると、containerscomponentsのどちらにもコンポーネントがあります。
それぞれのコンポーネントの違いは、Reduxへの依存しているかどうかで、依存関係をもったコンポーネントがSmart Components、そうでないコンポーネントをDumb Componentsと呼ぶそうです。

そして、Dumb ComponentsSmart Componentsからpropsで値を受け取ることが前提となります。

参考サイト

Reduxの設計で気をつけるところ – なっく日報

なんとなく分かったのでとにかく書いてみる

実行環境はgulp+webpackで構築しましたが、今回の記事では触れないので詳しくはリポジトリよりご確認ください。

Main

まずは、全てのファイルをまとめ上げる基点のファイルです。
Storeとルートコンポーネント(App)を関連付けています。

app.js

import React, {Component, PropTypes} from "react"
import ReactDOM from "react-dom"
import {Provider} from "react-redux"
import configureStore from "./store/configureStore"
import App from "./containers/App"


const store = configureStore();

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

Actions

1秒ごとに実行する予定のsyncDateアクションを定義します。
アクションタイプと一緒に、現在時刻を渡しています。

actions/clock.js

import {parseTime} from "../utils/dates"


export const SYNC_DATE = "SYNC_DATE";

export function syncDate() {
  const time = parseTime(new Date());
  return {
    type: SYNC_DATE, ...time
  };
}

後述しますが、utils/datesにあるparseTime()Dateオブジェクトから時間+分+秒をオブジェクトとして返す関数です。

Reducer

アクションから受け取った時刻のデータをStateに反映して返します。

reducers/clock.js

import {SYNC_DATE} from "../actions/clock"
import {parseTime} from "../utils/dates"


const initialState = parseTime(new Date());

export default function clock(state = initialState, action) {
  switch ( action.type ) {
    case SYNC_DATE:
      const {hour, minutes, seconds} = action;
      return Object.assign({}, state, {
        hour, minutes, seconds
      });
    default:
      return state;
  }
}

reducers/index.js

import {combineReducers} from "redux"
import clock from "./clock"


const reducer = combineReducers({
  clock
});

export default reducer;

今回は一つのReducerしかありませんが、combineReducersを使うことでそれぞれのReducerをまとめ上げることができるので便利っぽいですね!

Store

先ほど作ったReducerを渡して、Storeを作るためのconfigureStore()を定義します。
ここではアクション実行時に、データを分かりやすくコンソールに表示してくれるredux-loggermiddlewareに追加してみました。

store/configureStore.js

import {createStore, applyMiddleware} from "redux"
import logger from "redux-logger"
import reducer from "../reducers/"

const createStoreWithMiddleware = applyMiddleware(
  logger()
)(createStore);

export default function configureStore(initialState) {
  return createStoreWithMiddleware(reducer, initialState);
}

Utility

日付に関するもの、文字列の操作に関するものということで2つにファイルを分けてみましたが、それぞれ一つの関数しかありません。

utils/dates.js

export function parseTime(date=new Date()) {
  return {
    hour: date.getHours(),
    minutes: date.getMinutes(),
    seconds: date.getSeconds()
  };
}

utils/strings.js

export function zeroPadding(number, length = 1) {
  return (Array(length).join("0") + number).slice(-length);
}

Smart Component

Reduxとの依存関係をもったコンポーネントです。今回のルートコンポーネントにあたります。

componentDidMountでタイマーを設定しておいて、componentWillUnmountで解除するという点が今回のポイントかなと思います。

以下では1秒ごとにsyncDateアクションを呼び出しています。

containers/App.js

import React, {Component, PropTypes} from "react"
import {bindActionCreators} from "redux"
import {connect} from "react-redux"
import * as ClockActions from "../actions/clock"
import Clock from "../components/Clock"


@connect(
  state => ({time: state.clock}),
  dispatch => bindActionCreators(ClockActions, dispatch)
)
export default class App extends Component {
  static propTypes = {
    syncDate: PropTypes.func.isRequired,
    time: PropTypes.shape({
      hour: PropTypes.number.isRequired,
      minutes: PropTypes.number.isRequired,
      seconds: PropTypes.number.isRequired
    })
  };

  componentDidMount() {
    this.timer = setInterval(this.props.syncDate, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  render() {
    return (
      <div>
        <h1>Redux Clock</h1>
        <Clock {...this.props.time} />
        <p className="inspire">inspired by <a href="http://whatcolourisit.scn9a.org/">What colour is it?</a></p>
      </div>
    );
  }
}

次に書くClockコンポーネントには時間+分+秒の値をpropsとして渡しておきます。

Dumb Component

Reduxとの依存がなく、propsで親から値を受け取ることで動作するコンポーネントです。

渡ってきた時刻を使って、時刻の表示と背景色の適用を行っています。

components/Clock.js

import React, {Component, PropTypes} from "react"
import {zeroPadding} from "../utils/strings"


export default class Clock extends Component {
  static propTypes = {
    hour: PropTypes.number.isRequired,
    minutes: PropTypes.number.isRequired,
    seconds: PropTypes.number.isRequired
  };

  render() {
    let {hour, minutes, seconds} = this.props;
    hour = zeroPadding(hour, 2);
    minutes = zeroPadding(minutes, 2);
    seconds = zeroPadding(seconds, 2);

    const styles = {
      backgroundColor: `#${hour}${minutes}${seconds}`
    };

    return (
      <div className="clock" style={styles}>
        <span className="clock__time">{hour}:{minutes}:{seconds}</span>
        <span className="clock__color">#{hour}{minutes}{seconds}</span>
      </div>
    );
  }
}

まとめ

まだまだがっつり触れていないので、分からないことが沢山ありますが、ちょっとずつ時間を見つけて触ってみようと思います。