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

リスト表示+ページングを実装しながらReact.jsを理解してみる

More than 5 years have passed since last update.

jsonを返すAPIを使用して、チュートリアル+αの画面を実装してみます。

前提知識・単語

実装しながら読んだほうが理解しやすいと思います。

React.jsのadventカレンダー

http://qiita.com/advent-calendar/2014/reactjs

コンポーネント

画面を構成している要素。React.jsは画面をコンポーネントと呼ばれる要素に分けて実装します。
http://qiita.com/koba04/items/4f874e0da8ebd7329701

コンポーネントのライフサイクル

http://qiita.com/koba04/items/66e9c5be8f2e31f28461

propとstate

1. 画面をコンポーネント分けする

reactJSは画面を構成している要素ごとにパーツ分けをし、コンポーネントという呼び方をしています。

① リストを操作するコンポーネント

② リスト全体のコンポーネント

③ リスト1行のコンポーネント

2. ベースのhtmlを作成する

<html>
<head>
<!-- jQueryはなくてもreactJSは動きますが、このサンプルでは使うので読み込みます -->
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://fb.me/react-0.13.3.js"></script>
<script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
</head>
<body>
    <div id="content">
        <!-- ここに生成されたDOMが書き出されます -->
    </div>
    <script type="text/jsx">
        // ここにReactJSのコードを書いていきます
    </script>
</body>
</html>
apiのレスポンス
{
    total: 20,
    records: [
        {id:1, image:"hoge.jpg", name:"aa"},
        {id:2, image:"boge.jpg", name:"aba"},
        {id:3, image:"goge.jpg", name:"aca"},
        {id:4, image:"doge.jpg", name:"ada"},
        {id:5, image:"qoge.jpg", name:"aea"},
        {id:6, image:"eoge.jpg", name:"afa"},
        {id:7, image:"poge.jpg", name:"aga"},
        {id:8, image:"8oge.jpg", name:"aha"},
        {id:9, image:"0oge.jpg", name:"aia"},
        {id:10, image:"1oge.jpg", name:"aja"},
    ]
}

3. 各コンポーネントを実装する

上記のscriptタグの中に書いていきます。

画面描画部分
React.render(
    // ListBoxというコンポーネントを指定したタグに書き出します
    <ListBox />,
    document.getElementById('content')
);
①リストを操作するコンポーネント
var ListBox = React.createClass({
    // 初期値を設定します
    getInitialState: function() {
        return {data: [], page: 1, apiUrl: 'http://api.com/get'};
    },
    // ajaxのsuccessのfunctionにはbindが必要
    requestData: function() {
        $.ajax({
            url: this.state.apiUrl + '?page=' + this.state.page,
            dataType: 'json',
            cache: false,
            success: function(data) {
                // 既に表示しているデータと新たに取得したデータを連結して表示させる
                var oldData = this.state.data;
                var newData = oldData.concat(data.records);

                // setStateするとDOMが再構築されます
                this.setState({data: newData});
            }.bind(this)
        });
    },

    // スクロールイベント(ここらへんの実装はちょっと怪し目。。)
    onScroll: function(detail) {
        var scrollHeight = $(document).height();
        var scrollPosition = $(window).height() + $(window).scrollTop();

        // 画面最下部までスクロールしたタイミングで
        if ((scrollHeight - scrollPosition) / scrollHeight === 0) {
            // ページ番号更新
            this.setState({page: this.state.page + 1});
            // 次のページのデータを取得
            this.requestData();
        }
    },

    // このコンポーネントがDOMツリーに追加されたタイミングで呼ばれます
    componentDidMount: function() {
        // 1ページ目のデータを取得
        this.requestData();
        // ページングの処理を実装するためにスクロールイベントを追加
        window.addEventListener('scroll', this.onScroll);
    },

    render: function() {
        return (
            // SearchResultLitstのコンポーネントにdataを渡す
            <SearchResultList data={this.state.data} onScroll={this.onScroll} />                                                                                                
        );
    }
});
②リスト全体のコンポーネント
var SearchResultList = React.createClass({
    render: function() {
        // 行数分のリストを格納したノードを生成
        // 親ノードから受け取ったdataはpropsで受け取れます
        var resultNodes = this.props.data.map(function (row) {
            return (
                <SearchResult data={row} />
            );
        });

        return (
             <ul>
                {resultNodes}
             </ul>
        );
    }
});
③リスト1行のコンポーネント
var SearchResult = React.createClass({
    // class名を付けたい場合はclssNameを使います
    render: function() {
        return (
            <li>
                <div className="photo">
                    <img src={this.props.data.image} alt=""></img>
                </div>
                <div className="content">
                    <p className="name">{this.props.data.name}</p>
                </div>
            </li>
        );
    }
});

4. 完成

完成形
<html>
<head>
<!-- jQueryはなくてもreactJSは動きますが、このサンプルでは使うので読み込みます -->
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://fb.me/react-0.13.3.js"></script>
<script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
</head>
<body>
    <div id="content">
    </div>
    <script type="text/jsx">
        var SearchResult = React.createClass({
            // class名を付けたい場合はclssNameを使います
            render: function() {
                return (
                    <li>
                        <div className="photo">
                            <img src={this.props.data.image} alt=""></img>
                        </div>
                        <div className="content">
                            <p className="name">{this.props.data.name}</p>
                        </div>
                    </li>
                );
            }
        });

        var SearchResultList = React.createClass({
            render: function() {
                // 行数分のリストを格納したノードを生成
                // 親ノードから受け取ったdataはpropsで受け取れます
                var resultNodes = this.props.data.map(function (row) {
                    return (
                        <SearchResult data={row} />
                    );
                });

                return (
                     <ul>
                        {resultNodes}
                     </ul>
                );
            }
        });

        var ListBox = React.createClass({
            // 初期値を設定します
            getInitialState: function() {
                return {data: [], page: 1, apiUrl: 'http://api.com/get'};
            },
            // ajaxのsuccessのfunctionにはbindが必要
            requestData: function() {
                $.ajax({
                    url: this.state.apiUrl + '?page=' + this.state.page,
                    dataType: 'json',
                    cache: false,
                    success: function(data) {
                        // 既に表示しているデータと新たに取得したデータを連結して表示させる
                        var oldData = this.state.data;
                        var newData = oldData.concat(data.records);

                        // setStateするとDOMが再構築されます
                        this.setState({data: newData});
                    }.bind(this)
                });
            },

            // スクロールイベント
            onScroll: function(detail) {
                var scrollHeight = $(document).height();
                var scrollPosition = $(window).height() + $(window).scrollTop();

                // 画面最下部までスクロールしたタイミングで
                if ((scrollHeight - scrollPosition) / scrollHeight === 0) {
                    // ページ番号更新
                    this.setState({page: this.state.page + 1});
                    // 次のページのデータを取得
                    this.requestData();
                }
            },

            // このコンポーネントがDOMツリーに追加されたタイミングで呼ばれます
            componentDidMount: function() {
                // 1ページ目のデータを取得
                this.requestData();
                // ページングの処理を実装するためにスクロールイベントを追加
                window.addEventListener('scroll', this.onScroll);
            },

            render: function() {
                return (
                    // SearchResultLitstのコンポーネントにdataを渡す
                    <SearchResultList data={this.state.data} onScroll={this.onScroll} />                                                                                                
                );
            }
        });

        React.render(
            // ListBoxというコンポーネントを指定したタグに書き出します
            <ListBox />,
            document.getElementById('content')
        );
    </script>
</body>
</html>

所感

  • コンポーネントという考え方は、DOM操作が非常にやりやすい
  • それぞれのコンポーネントが独立していて、コンポーネントごとに処理が分かれているので、道筋がたてやすく実装しやすい
  • コードの中にhtml(厳密にはjsx)が混ざってしまって、元のhtmlからの書き直しにコストがかかる

リンク

musclemikiya
iOS / Android / Web Engineer (PHP/JavaScript)
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
No 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
ユーザーは見つかりませんでした