jsonを返すAPIを使用して、チュートリアル+αの画面を実装してみます。
前提知識・単語
実装しながら読んだほうが理解しやすいと思います。
React.jsのadventカレンダー
コンポーネント
画面を構成している要素。React.jsは画面をコンポーネントと呼ばれる要素に分けて実装します。
http://qiita.com/koba04/items/4f874e0da8ebd7329701
コンポーネントのライフサイクル
propとstate
- http://qiita.com/koba04/items/bc13d1f42964278ae14e (prop)
- http://qiita.com/koba04/items/63267bcc918d76ac8767 (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からの書き直しにコストがかかる