reactjs
redux
SansanDay 9

何をreduxのコンテナにするか

More than 1 year has passed since last update.

redux最近来てるっぽい

reduxとreactを使ってみて、コンテナ(Smart Component)とコンポーネント(Dumb Component)をどう使い分けるかについて考えてみた。

https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.p8sdryhie

上はreduxのcontributor自身による使い分けに付いての解説記事。

各種単体での使ってみた感想はこちら
redux
react

react-redux

reactはJust UIだし、reduxはアプリの状態管理のみが関心ごとなので、reactとreduxを組み合わせるためには、https://github.com/rackt/react-redux をつかいます。

これの役割は何かと言うと、reduxが管理している状態やactionCreatorと、reactのコンポーネントとの橋渡しです。

結合すると、reactコンポーネントで何かイベントが起きる(=reduxのactionCreatorが呼ばれる)と、reduxのアクションに応じてredux管理のアプリの状態が変わり、その結果がreactコンポーネントのpropsに渡って再描画される、というイメージです。この橋渡しはconnectメソッドをつかって行われ、reduxとconnectされたcomponentはコンテナと呼んだりしています。

何をコンテナにするか?

react-reduxのドキュメントを最初読んだときに、データフローをわかりやすくするためにコンテナはネストさせない方が良い、的なことが書いてあり、最初愚直にそれに従いました。その結果どうなったかというと、

class RootComponent extends Component {
  render() {
    const {prop1, prop2, prop3, prop4, prop5} = this.props;
    const {action1, action2, action3, action4, action5} = this.props;
    return (
      <div>
        <SubComponent
            prop1={prop1}
            prop2={prop2}
            prop3={prop3}
            action1={action1}
            action2={action2}
            action3={action3}
        />
        <SubComponent
            prop4={prop4}
            prop5={prop5}
            action4={action4}
            action5={action5}
        />
      </div>
    );
  }
}

connect(
  mapStateToProps,
  mapDispatchToProps
)(RootComponent);

的な感じで、子要素で必要なものを全てルートで渡す事になり、トップのコンポーネントのpropsが肥大化し、さらに深い階層のコンポーネントに必要なアクションもルートで渡す必要があるため、直感的になんでこのアクションを渡しているのかよくわからん感じになります。

データフローがネストしなければ良い

結局ドキュメントで言っているのはデータフローをネストさせるな、という話なので、actionに関しては何も言ってないです。

そんなわけで、渡すpropsのデータがネストしないのであれば、むしろ積極的にコンテナは分けていった方がよさそうです。

たとえば商品リストを一覧で表示し、追加フォームや削除ボタンがあるとします。

containerになるのは、

<ItemList items={items}/>
<NewItemForm addItem={addItem}/>
<DeleteItemButton deleteItem={deleteItem}/>

です。itemsaddItemdeleteItemはそれぞれreduxの管理している状態とアクションです。

ただのコンポーネントしては、

<Item item={item}/>

だけです。itemについては、ItemListからpropsで与えられる物とします。

実装としては、

class ItemList extends Component {
  render() {
    return (
      <div>
        <NewItemForm/>
        {this.props.items.map((item) => {
          return <Item key={item.id} item={item}/>
        })}
      </div>
    );
  }
}

class Item extends Component {
  render() {
    return (
      <div>
        {item.title} {item.price}円 <DeleteItemButton props={item.id}/>
      </div>
    );
  }
}

といった感じになるかと。ルートになるItemListには表示する情報だけを渡し、ネストした要素で必要な削除や追加などのアクションは、アクションだけをreduxとconnectしたコンテナをつかい、必要に応じてpropsでID等を渡します。各コンポーネントに渡されるpropsは直感的にそのコンポーネントに必要そうなものしか与えられないので、ずいぶんわかりやすくなったと思います。

結論

データフローだけネストしないように気をつけて、actionだけがreduxに依存するようなコンポーネントは積極的にcontainerにしていったほうが見通しよくなりそう。