Help us understand the problem. What is going on with this article?

react/fluxでpropsのバケツリレーを避ける

More than 3 years have passed since last update.

reactにfluxを採用してアプリケーションを設計していると、親->子->孫コンポーネントへpropsのバケツリレーになってしまう事がありました。
そもそもイケてない設計が悪いのですが、reactバケツリレーつらいという声を聞くことも多かったし、自分も辛いと思っていたところ助言をいただいて解決法が見えてきたのでまとめてみます。

propsバケツリレー

まず、バケツリレーを再現してみます。

DOM構造

おおまかにdiv[class="app"](親), div[class="group"](子), div[class="item"](孫)というツリー構造になってます。

<div class="app"><!--親-->
    <div class="group"><!--子-->
        <h2>1番目のグループ</h2>
        <p class="count">13</p>
        <div class="items">
            <div class="item"><!--孫-->
                ...
            </div>
            <div class="item"><!--孫-->
                ...
            </div>
            ...
        </div>
    </div>
    <div class="group"><!--子-->
        <h2>2番目のグループ</h2>
        <p class="count">5</p>
        <div class="items">
            <div class="item"><!--孫-->
                ...
            </div>
            <div class="item"><!--孫-->
                ...
            </div>
            ...
        </div>
    </div>
    ...
</div>

で、これをComponent化するとそのまま<App />, <Group />, <Item />みたいに分けることができるんじゃないかと考えます。

Storeが保持するデータ構造

Storeが保持するデータ構造も同じで複数のgroup(子)にぶら下がるitem(孫)達という感じです。

{
    "group-1": [{item1-1}, {item1-2}, {item1-3}, ...],
    "group-2": [{item2-1}, {item2-2}, {item2-3}, ...],
    ...
}

App(RootComponent)

実際のComponentはどうなるかというと、
AppはStoreをsubscribeするRootComponentとして実装されこんな感じ。

var Group = require("./Group.jsx");
var App = React.createClass({
    getInitialState(){
        return {
            groups: Store.getAll()
        }
    },
    render(){
        var groups = Object.keys(this.state.groups).map((key) => {
            return <Group items={this.state.group[key]} title={key} />;
        });
        return (
            <div className="app">
                {groups}
            </div>
        );
    },
    ...
});

Group

GroupからItemへ値のリレー

var Item = require("./Item.jsx");
var Group = React.createClass({
    render(){
        var items = this.props.items.map((item) => {
            return <Item {...item} />
        });
        return (
            <div className="child">
                <h2>{this.props.title}</h2>
                <p className="count">{this.props.items.length}</p>
                <div className="items">
                    {items}
                </div>
            </div>
        );
    }
});

このようにDOM構造と同じだけReactComponentも入れ子していってしまうと、
propsのバケツリレーで面倒なだけでなく、Itemへ渡したい値は必ずGroupを通らねばならず、GroupはItemの設計に依存してしまいます。
これではGroupはテストも作りにくく変更にも弱いですね。

propsのバケツリレーを無くすために

依存を無くす

子孫間の依存を無くすために、<Item />に渡すpropsを<App />から直接渡す形に変更します。

var Group = require("./Group.jsx");
var Item = require("./Item.jsx");
var App = React.createClass({
    getInitialState(){
        return {
            groups: Store.getAll()
        }
    },
    render(){
        return (
            <div className="app">
                {this.renderGroups()}
            </div>
        );
    },
    renderGroups(){
        var groups = Object.keys(this.state.groups);
        return groups.map((key) => {
            return (
                <Group count={groups[key].items.length} title={key}>
                    {this.renderItems(key)}
                </Group>
            );
        });
    },
    renderItems(key){
        var items = this.state.groups[key];
        return items.map((item) => {
            return <Item {...item} />;
        });
    },
    ...
});
var Group = React.createClass({
    render(){
        return (
            <div className="group">
                <h2>{this.props.title}</h2>
                <p className="count">{this.props.count}</p>
                <div className="items">
                    {this.props.children}
                </div>
            </div>
        );
    }
});

変更のポイントは<Group>{items}</Group>という形で<Item />(の集合)を直接Componentとして渡すことです。
このように配置された場合、itemsは<Group />this.props.childrenに入るので、そのまま任意の場所に流し込めばよいでしょう。
この変更により<Group />に渡されるComponentは何の縛りもなくなり、ただ自分の管轄であるtitleとcountだけを受け取ればいい形になりました。

当たり前にやってる人にはなんの驚きもないとおもいますが、自分はそもそもpropsにComponentを渡すという発想が希薄だったので、これは目から鱗でした。アドバイスを下さった @axross_ さんに感謝m(_ _)m

もっとこうしたほうがいいよとかありましたら、是非コメントをください。

参考

コンポーネント指向による、Reactのベストプラクティスとバッドプラクティス

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away