maesblog

Reactのバケツリレーの回避にJSX Spread Attributesを利用する

Reactは、データは必ず親から子にしか渡せない仕組みになっているため、バケツリレーが随所で発生します。階層が深くなればなるほど、バケツリレーは大変になります。さらに渡せるデータは単なる値のみならず、オブジェクトやら関数など何でもありということから、気がついたらバケツリレーがとんでもなく複雑になっていたということが起こりがちです。今回は、そんなReactのバケツリレーに疲れた時に試してもらいたい一つの方法を紹介したいと思います。

Reactのバケツリレーとは

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

  • Appをルートコンポーネントとし、以下Child1 => Child2 => Child3といった階層構造となっている
  • Appで受け取ったfooとbarという値をpropsを使って、一番下の階層のChild3まで渡している
  • 値がChild3まで渡ったら、それをChild3のコンポーネント上で表示する

コードは以下のようになります。

import React, { Component } from 'react';
import { render } 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>
);

render(
  <App foo="foo" bar="bar" />,
  document.querySelector('.content')
);

上記のコードをビルドしてブラウザに表示したとすると、以下のような表示結果となります。

foo
bar

propsを使ったバケツリレーが行われていることがわかるかと思います。

Reactのバケツリレーの辛いところ

では、次にAppに渡すデータを一つ増やして、Child3で追加されたデータも表示させるようにしてみましょう。新たにbazという値を渡して、foo、bar、bazの3つ値をブラウザに表示させるようにします。普通に書くとコードは以下のようになるかと思います。

import React, { Component } from 'react';
import { render } 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>
);

render(
  <App foo="foo" bar="bar" baz="baz" />,
  document.querySelector('.content')
);

表示結果は予想通り以下のようになります。

foo
bar
baz

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

アプリケーションの規模が大きくなればなるほど、これは辛くなるなということが予想できるんじゃないでしょうか。(上記のサンプルでは、便宜上propTypesのバリデーションを付けていませんが、本来はpropを扱うコンポーネントは全てバリデーションを実装することになるので、余計複雑になることは目に見えます。)

JSX Spread Attributesを使う

そこで今回紹介したいのが、JSX Spread Attributesというものです。

以下のコードはReactのドキュメントで説明されているコードですが、コンポーネントのDOMの中に{…オブジェクト名}と書くと、指定したオブジェクト名でオブジェクトのプロパティをまとめて下の階層に渡すことができるようになります。

var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
console.log(component.props.foo); // => 'x'
console.log(component.props.bar); // => 'y'

このように書いておくと、オブジェクトのプロパティを追加したり削除したりするときに、コンポーネントのDOMをいじる必要がなくなり、だいぶ辛さがなくなります。

ちなみにこのは、ES6(ES2015)の配列でサポートされているSpread Operatorというものになります。詳細は以下をご参照ください。

JSX Spread Attributesを使って書き直す

サンプルのコードをJSX Spread Attributesを使って書き直すと、以下のようにだいぶスッキリします。{…props}で渡し、propsで受け取るといった感じになります。

import React, { Component } from 'react';
import { render } 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>
);

/* 渡すデータ */
const props = {
  foo: 'foo',
  bar: 'bar',
  baz: 'baz',
};

render(
  <App {...props} />,
  document.querySelector('.content')
);

表示結果も変わらず、ちゃんとデータが上から下まで伝わっていることがわかるでしょう。

foo
bar
baz

渡すデータを増やす場合

JSX Spread Attributesを使っている場合、渡すデータを増やすとしたら、どのようにすればいいか見てみましょう。渡すデータにquxという文字列を追加してみます。

コードは以下のようになります。どうでしょうか。変更する部分はマーカーをつけていますが、最終的にデータをDOMに反映するコンポーネント(Child3)のみとなっていることがわかると思います。

import React, { Component } from 'react';
import { render } 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>
);

/* 渡すデータ */
const props = {
  foo: 'foo',
  bar: 'bar',
  baz: 'baz',
  qux: 'qux',
};

render(
  <App {...props} />,
  document.querySelector('.content')
);

表示結果にちゃんと追加したquxが反映されています。

foo
bar
baz
qux

propのバリデーションをつける

Reactでは、propTypesプロパティを使って、propに対してバリデーションをつけることができます。最初に紹介した通常の書き方であればpropを扱うコンポーネントは全てバリデーションを実装することになり大変面倒です。しかしJSX Spread Attributesを使えば、バリデーションも、最終的にデータをDOMに反映するコンポーネント(Child3)のみ実装するだけで済むようになります。

バリデーションを実装したコードです。かなりシンプルに書けていると思います。

import React, { Component } from 'react';
import { render } 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>
);

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

/* 渡すデータ */
const props = {
  foo: 'foo',
  bar: 'bar',
  baz: 'baz',
  qux: 'qux',
};

render(
  <App {...props} />,
  document.querySelector('.content')
);

JSX Spread Attributesなら上書きも簡単

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

var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // => 'override'

このJSX Spread Attributesのオーバーライドの特性を利用して、以下のように書くとバケツリレーの途中のコンポーネントにおいて、渡すデータを上書きすることができます。

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

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

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

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>
);

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

/* 渡すデータ */
const props = {
  foo: 'foo',
  bar: 'bar',
  baz: 'baz',
  qux: 'qux',
};

render(
  <App {...props} />,
  document.querySelector('.content')
);

表示結果もちゃんと上書きされた状態で出力されます。

aaa
bbb
baz
qux

まとめ

JSX Spread Attributesを使うと、渡すデータに変更があっても、コードの変更をかなり少なくすることができます。これでだいぶ辛さが解消できるのではないでしょうか。属性のマージも簡単に行えるし、利用価値はそれなりに高いんじゃないかなと思っています。

Reactを書く上で、バケツリレーは切っても切り離せないものとなっています。このバケツリレーで、けっこう消耗している方もいるかと思います。バケツリレーを回避する方法は、Googleで「React バケツリレー」などで検索すればたくさん出てくると思いますので、今回紹介した方法も参考にしながら、いろいろお試しいただければと思います。

入門 React ―コンポーネントベースのWebフロントエンド開発
  • 『入門 React ―コンポーネントベースのWebフロントエンド開発』
  • 著者: Frankie Bagnardi, Jonathan Beebe, Richard Feldman, Tom Hallett, Simon HØjberg, Karl Mikkelsen, 宮崎 空(翻訳)
  • 出版社: オライリージャパン
  • 発売日: 2015年4月3日
  • ISBN: 978-4873117195

関連記事

コメント

  • 必須

コメント