Reactコンポーネントをファイル分割したら沈黙してしまった件

Reactで大きな画面を作るうちに、人の習性としてReactコンポーネントをファイル分割したくなります。

var React = require('react');

var Item = React.createClass({
    render: function () {
        return <li value={this.props.value}>{this.props.text}</li>;
    }
});

var List = React.createClass({
    render: function() {
        var items = this.props.data.map(function(i) {
            return <Item {...i}/>
        });
        return (
            <ol>{items}</ol>
        );
    }
});

var data = [
    {text: 'One', value: 1},
    {text: 'Four', value: 4},
    {text: 'Five', value: 5},
    {text: 'Seven', value: 7}
];

var ReactDOM = require('react-dom');
ReactDOM.render(<List data={data}/>, document.getElementById('List'));

と、まあこんなサンプルがあったとして、気持ち悪いのはコンポーネントの宣言記述と、その利用が同じファイルでやってること。Reactの説明で多くの場合はコンポーネントの宣言直下でReactDOM.render()を行ってますが、まあ実践では違う作りになるんじゃないかなと。で、以下みたいにするとします。

// List.jsx
var React = require('react');

var Item = React.createClass({
// 省略
});

module.exports = List = React.createClass({
// 省略
});
  • 描画部の別ファイル
var data = [
// 省略
];

var ReactDOM = require('react-dom');
var List = require('./List.jsx');
ReactDOM.render(<List data={data}/>, document.getElementById('List'));

汎用たるコンポーネントと、個別たる描画利用部が分かれていい感じ。requireもreactとreact-domがそれぞれで、なるほどこれがデザインかと。しかしこれらをBrowserifyでまとめたらさっきまで動いていたのに沈黙してしまいました。

しばらく嵌るうちにわかったのは、Listコンポーネントがexportされているけど、その中で使われているItemコンポーネントがexportされていないから。全部同じファイルだったら見えるし、描画-List-Itemと三つのファイルに分割する(もちろんItemも適切にexportする)のならば動きます。ただし描画部で、ReactDOM.render()の引数に渡しているJSXがTranspileした結果、React.createElement(List, {data: data})と変換されているので、Reactをコード上でてこなくてもrequireしなくちゃならなそうだったりと新たな痒さも。

コンパイラ言語だったり、JavaScriptでもクロージャーみたいに、スコープ見えるはずと思っていてもBrowserifyによる力技なコピーかき集め作業の前には通用しなかった。Reactコンポーネントはファイル毎にひとつづつってのがReact-Lintも警告するような常識だったようですが、かといって細かいファイルたくさんは作りたくない。。。はてさて。