LoginSignup
4
4

More than 5 years have passed since last update.

Reactのチュートリアル(TODOアプリ)に alt を適用した

Posted at

背景

flux を使ってみたくて、最初はフレームワークを使わずに実装してみた。特に不満はないものの、フレームワークを使ってみるとどのような効果があるのかな?と試してみたくて、altを適用してみた。
altは、活発にメンテナンスされており、商用としてつかわれている実績もあるようだ。

  • It is pure flux. Stores have no setters, the flow is unidirectional.
  • Isomorphic and works with react-native.
  • Actively maintained and being used in production.
  • Extremely flexible and unopinionated in how you use flux. Create traditional singletons or use dependency injection.
  • It is terse. No boilerplate.

感想

altを少しかじっていないと、書いてあることの意味が掴めないかもしれません。

ストレートにFluxを適用するよりも便利だな、と感じたこと

アクションタイプをわざわざ定数化したり、ボイラープレートを用意する必要がない

アクションを定義するためにストア内部に実装するのは、

destroy (id) {
return id;
}

これだけ。altが公開している関数 createActions に包むことで、自分が実装したアクションを強化してくれるから、これだけで済む。
ちなみに普通に書くとこのようになる。

destroy: (id) => {
  AppDispatcher.dispatch({
    actionType: TodoConstants.TODO_DESTROY,
    id: id
  });
}
import keyMirror from 'keyMirror';

module.exports = keyMirror({
  TODO_CREATE: null,
  TODO_COMPLETE: null,
  TODO_DESTROY: null,
  TODO_DESTROY_COMPLETED: null,
  TODO_TOGGLE_COMPLETE_ALL: null,
  TODO_UNDO_COMPLETE: null,
  TODO_UPDATE_TEXT: null
});

actionType 向けに、定数を用意している。また、ディスパッチするために AppDispatcher.dispatchを呼び出している。

ストア内部の switch を記述しなくても、ハンドラとバインドする仕組みがある

ストア内部に、 bindListener を記述することで、 alt がハンドラとアクションを関連づけてくれる。

this.bindListeners({
  handleCreate: TodoActions.CREATE,
  handleUpdateText: TodoActions.UPDATE_TEXT,
  handleToggleComplete: TodoActions.TOGGLE_COMPLETE,
  handleTodoUndoComplete: TodoActions.TODO_UNDO_COMPLETE,
  handleTodoComplete: TodoActions.TODO_COMPLETE,
  handleToggleCompleteAll: TodoActions.TOGGLE_COMPLETE_ALL,
  handleDestroy: TodoActions.DESTROY,
  handleDestroyCompleted: TodoActions.DESTROY_COMPLETED,
  handleUpdateTodos: TodoActions.UPDATE_TODOS,
  handleTodoFailed: TodoActions.TODO_FAILED,
  handleFetchTodos: TodoActions.FETCH_TODOS
});

普通は、このような switch文を用意する。

dispatcherIndex: AppDispatcher.register(function(payload) {
  var action = payload.action;
  var text;

  switch(action.actionType) {
    case TodoConstants.TODO_CREATE:
      text = action.text.trim();
      if (text !== '') {
        create(text);
        TodoStore.emitChange();
      }
      break;

    case TodoConstants.TODO_DESTROY:
      destroy(action.id);
      TodoStore.emitChange();
      break;

    // add more cases for other actionTypes, like TODO_UPDATE, etc.
  }

  return true; // No errors. Needed by promise in Dispatcher.
})

ストア内部で change イベントを明示的に発火しなくて良い

Stores automatically emit a change event when an action is dispatched through the store and the action handler ends.

上記のコードからもわかるように、change イベントを明示的に記述する必要はない。ストア内部で自分が書くコードは、特定のアクションのハンドラだけである。

//単純に、ペイロードとして渡される値について処理するだけ
const destroy = (id) => {
  delete _todos[id];
}

非同期通信時の状態をコントロールできる

ソースモジュールに、非同期通信のローディング時、成功時、失敗時のそれぞれに対応するアクションを設定できる。また、isLoading()を使うことでコンポーネントを制御し、ローディング中は画像を表示させるなどの分岐を簡単に、Functional に実装することができる。

// 非同期処理を扱う、ソースモジュール
// 非同期であるfetchTodos関数を、success/error/loading にそれぞれ関連付けたアクションを使って実現する
// 
const TodoSource = {
  fetchTodos() {
    return {
      remote(state) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            if (true) {
              resolve(mockData);
            }else {
              reject('Things have broken');
            }
          }, 500);
        });
      },

      local() {
        return null;
      },

      success: TodoActions.updateTodos,
      error: TodoActions.todoFailed,
      loading: TodoActions.fetchTodos,

      shouldFetch(state){
        return true;
      }
    };
  }
}
// Component#render()内のコード
if(TodoStore.isLoading()) {
  return (
    <div>
      <img src="ajax-loader.gif" />
    </div>
  )
}

altの思想

Fluxの考え方を踏襲している。

The basic idea is that you have a container that wraps your component, the duty of this container component is to handle all the data fetching and communication with the stores, it then renders the corresponding children. The sub-components just render markup and are data agnostic thus making them highly reusable.

コンテナとなるコンポーネント以外は状態を持つことなく、与えられたデータによってのみDOMをrenderすることで、コンポーネントは高い再利用性を持つ。これは alt に限ったことではなく、 Flux の思想と言える。

mixinというものに初めて触れた

altのStoreMixinというモジュールによって、いろいろと便利な関数が自分の書いたストアに追加される。
これがどのようなことを意味するかというと、全く定義していない(ように思える)関数を、自分がストアに記述するということになる。実行時にこの関数が追加されるため、コードを書いている時点では少々、頭の中にハテナが浮かぶ。これは慣れるしかないか。

ただし、ES6からは mixin をサポートする機能が React から落とされているので、今後は別の手段で問題解決するのが主流となる。

HOC (High Order Component) に触れた

別の手段というのが、この HOC のことで、詳しくはこちらに書いてある。
Mixins are Dead.

HOCの特徴は合成で、React ではあるコンポーネントを起点として、新たなコンポーネントを作ることを指す。この合成は繰り返すことができる点も利点としてあげられる。

ところで、もともと mixin や HOC は、Reactにおいては大きく2つの重要な役割を担っている。

  • ユーティリティ関数を提供する
  • 共通のライフサイクル関数( lifecycle hook )を提供する

その際に、mixinの持つ次のような課題を解消する期待を持たれているのが HOC ということだ。

  • mixin とコンポーネントとの依存関係が暗黙的
  • 一つのコンポーネントに mixin を多用するとクラッシュする可能性がある
  • mixin は、本来できる限り少なくすべきstateを増やす傾向がある
  • パフォーマンスチューニングを複雑にする

React準拠でドラッグアンドドロップを制御するReact DnDも似たような作りになっている。

テストコード

まだ書いていない・・。

まだちょっと理解が追いついていないこと

. BootStrap/Snapshot などのAPI
. Isomorphic な alt の利用シーン
. altjsのリポジトリの使い方

サンプルコード

https://facebook.github.io/react/docs/flux-todo-list.html
このチュートリアルのコードを、次のステップで修正していった。

  1. ES6化
  2. alt の適用
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4