典型的な React アプリケーションでは、データは props を通してトップダウン(親から子)で渡されます。ツリーの各階層で明示的にプロパティを渡し、コンポーネント間で値を共有します。

親から子へ値を渡すことを一般的に バケツリレー と呼んでいます。

React のバケツリレーとは

以下のようなコンポーネントがあったとします。

  • App をルートコンポーネントとし、Child1 => Child2 => Child3 といった階層構造となっている
  • App で受け取った foo と bar という値を props にして、一番下の階層のChild3 まで渡す
  • props が Child3 まで渡ったら、それを Child3 のコンポーネントで表示する
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
  render() {
    return (
      <Child1
        foo={this.props.foo}
        bar={this.props.bar}
      />
    );
  }
}

const Child1 = (props) => <Child2
  foo={props.foo}
  bar={props.bar}
/>;

const Child2 = (props) => <Child3
  foo={props.foo}
  bar={props.bar}
/>;

const Child3 = (props) => (
  <div>
    <div>{props.foo}</div>
    <div>{props.bar}</div>
  </div>
);

ReactDOM.render(
  <App foo="foo" bar="bar" />,
  document.getElementById('root')
);

上記のコードをビルドしてブラウザに表示したところ

バケツリレーがなぜ問題か

では、次に App に渡すデータを一つ増やして、Child3 で追加されたデータも表示させるようにしてみましょう。新たに baz という値を渡して、foo、bar、baz をブラウザに表示します。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
  render() {
    return (
      <Child1
        foo={this.props.foo}
        bar={this.props.bar}
        baz={this.props.baz}
      />
    );
  }
}

const Child1 = (props) => <Child2
  foo={props.foo}
  bar={props.bar}
  baz={props.baz}
/>;

const Child2 = (props) => <Child3
  foo={props.foo}
  bar={props.bar}
  baz={props.baz}
/>;

const Child3 = (props) => (
  <div>
    <div>{props.foo}</div>
    <div>{props.bar}</div>
    <div>{props.baz}</div>
  </div>
);

ReactDOM.render(
  <App foo="foo" bar="bar" baz="baz" />,
  document.getElementById('root')
);

上記のコードをビルドしてブラウザに表示したところ

リレーするだけのコンポーネント Child1、Child2 に変更が発生しています。つまりルートコンポーネントから下の階層に渡すデータに変更があったら、最終的にそのデータの変更の影響を受けるコンポーネントのみならず、その通り道となるコンポーネントにおいてもその都度変更が発生することになります。

JSX Spread Attributesを使う

この問題を解決する方法こそが、JSX Spread Attributes です。 コンポーネントの DOM の中に { ...<object_name> } と書くと、指定したオブジェクト名でオブジェクトのプロパティをまとめて下の階層に渡すことができるようになります。オブジェクトのプロパティを追加したり削除したりするとき、コンポーネントの DOM を書き換える必要がありません。

function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];

console.log(sum(...numbers));
// expected output: 6

console.log(sum.apply(null, numbers));
// expected output: 6

 

JSX Spread Attributes でリライト

JSX Spread Attributes を使いリライトします。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
  render() {
    return (
      <Child1 { ...this.props } />
    );
  }
}

const Child1 = (props) => <Child2 { ...props } />;

const Child2 = (props) => <Child3 { ...props } />;

const Child3 = (props) => (
  <div>
    <div>{props.foo}</div>
    <div>{props.bar}</div>
    <div>{props.baz}</div>
  </div>
);

ReactDOM.render(
  <App foo="foo" bar="bar" baz="baz" />,
  document.getElementById('root')
);

props の仕様変更に柔軟に対応

渡すデータに qux を追加するときは、コンポーネント(Child3)のみ変更すればよいです。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
  render() {
    return (
      <Child1 { ...this.props} />
    );
  }
}

const Child1 = (props) => <Child2 { ...props} />;

const Child2 = (props) => <Child3 { ...props} />;

const Child3 = (props) => (
  <div>
    <div>{props.foo}</div>
    <div>{props.bar}</div>
    <div>{props.baz}</div>
    <div>{props.qux}</div>
  </div>
);

ReactDOM.render(
  <App foo="foo" bar="bar" baz="baz" qux="qux" />,
  document.getElementById('root')
);

上記のコードをビルドしてブラウザに表示したところ

JSX Spread Attributesなら上書きも簡単

JSX Spread Attributesを使うと、他の属性とマージすることも簡単に行えます。コンポーネントのDOMに複数属性を指定した場合、後に記述した属性が、前の属性を上書き(オーバーライド)するようになっており、これを利用することで以下のようにSpread Attributesを上書きすることが可能です。このJSX Spread Attributesのオーバーライドの特性を利用して、以下のように書くとバケツリレーの途中のコンポーネントにおいて、渡すデータを上書きすることができます。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

class App extends Component {
  render() {
    return (
      <Child1 {...this.props} qux={true} />
    );
  }
}

const Child1 = (props) => <Child2 { ...props} />;

const Child2 = (props) => <Child3 { ...props} />;

const Child3 = (props) => (
  <div>
    <div>{props.foo}</div>
    <div>{props.bar}</div>
    <div>{props.baz}</div>
    <div>{props.qux ? 'TRUE' : 'FALSE'}</div>
  </div>
);

// props のバリデーション
Child3.propTypes = {
  foo: PropTypes.string,
  bar: PropTypes.string,
  baz: PropTypes.string,
  qux: PropTypes.bool,
};

ReactDOM.render(
  <App foo="foo" bar="bar" baz="baz" qux="1" />,
  document.getElementById('root')
);

上記のコードをビルドしてブラウザに表示したところ