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

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
* http://qiita.com/koba04/items/bc13d1f42964278ae14e (prop)
* http://qiita.com/koba04/items/63267bcc918d76ac8767 (state)



## 1. 画面をコンポーネント分けする
reactJSは画面を構成している要素ごとにパーツ分けをし、コンポーネントという呼び方をしています。
### ① リストを操作するコンポーネント
### ② リスト全体のコンポーネント
### ③ リスト1行のコンポーネント

## 2. ベースのhtmlを作成する
```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>
```


```json: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タグの中に書いていきます。

```javascript:画面描画部分
React.render(
	// ListBoxというコンポーネントを指定したタグに書き出します
	<ListBox />,
	document.getElementById('content')
);
```

```javascript:①リストを操作するコンポーネント
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} />                                                                                                
		);
	}
});
```

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

```javascript:③リスト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:完成形
<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からの書き直しにコストがかかる

## リンク
* [本家のチュートリアル](https://facebook.github.io/react/docs/tutorial.html)
* [React 雑感](http://qiita.com/naoya@github/items/a4d54f5ab008c3fa4055)
* [なぜ仮想DOMという概念が俺達の魂を震えさせるのか](http://qiita.com/mizchi/items/4d25bc26def1719d52e6)