React.jsと組み合わせるfluxのライブラリreduxを試してみた。 このページは作業ログです。やったことをつらつら書くだけなのでまとめません。あしからず。
対象読者
- 主に自分
今日のゴール
- Reactの構成を理解できていること
- Reactコンポーネントのイベントハンドラの設定が理解できていること
やったことメモ
Reactのドキュメントを読む
チュートリアル(日本語)。に目を通しつつ、手を動かす。
感想
- 手を動かしながら学べるのは良い
-
<script type="text/jsx">
の中に実装をしていく?まず、tutorial1.jsを実装してみる。 - コンポーネントの定義ができる下記のようなスニペットがあるとよさそう
var Component = React.createClass({
render : function(){
return (
// write JSX
);
}
});
- ES6だと下記のように記述できる
import {Component} from 'react';
class Hoge extends Component {
render() {
return (
// write JSX
);
}
}
- Componentに対するイベントハンドラは下記のように定義したクラスの中のfunctionで定義できる。
onSubmit={this.handleSubmit}
の辺りが該当コード。
var CommentForm = React.createClass({
handleSubmit : function(e) {
// ...
},
render : function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder="Your Name..." ref="author"/>
<input type="text" placeholder="Say somethings..." ref="text"/>
<input type="submit" value="Post"/>
</form>
);
}
});
- ~~
this.props
ってもしかして親コンポーネントなのか?~~→違う自Componentのプロパティ。<CommentForm onCommentSubmit={this.handleCommentSubmit}/>
の例だと、呼び出し元でイベントハンドラonCommentSubmit
が追加されている。
var CommentForm = React.createClass({
handleSubmit : function(e) {
e.preventDefault();
var author = React.findDOMNode(this.refs.author).value.trim();
var text = React.findDOMNode(this.refs.text).value.trim();
if (!author || !text) {
console.log('invalid value');
return;
}
// TODO: submit data to server
this.props.onCommentSubmit({author: author, text: text}); // onCommentSubmitはCommentFormに追加したイベントハンドラ
React.findDOMNode(this.refs.author).value = '';
React.findDOMNode(this.refs.text).value = '';
return;
},
チュートリアル以外
Reusable Component
Classの定義
- ES6環境ではコンポーネントは下記のように継承を使って定義可能
class HelloMessage extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
propTypes
- コンポーネントにプロパティを設定した時にバリデーションする
- 開発時のみ有効になるらしい→プロダクション環境への切り替えはどうやるの?
- 型チェックやら必須チェックやら
- 新規に関数を作成したらカスタムバリデータとして機能
Reduxのチュートリアルに戻る
2.5 Reactとつなげる
AddTodo
、TodoList
、Todo
、Footer
どれもDumb Component。データがどこから来てどう変更するのか知らなくてもOKなので、Reduxに依存しなくても大丈夫。
ReactのコンポーネントとReduxを接続するには
-
react-redux
を依存に追加 - 下記の
index.js
のようにSmart Componentであるコンテナを<Provider>
でラップする(Providerはreact-redux/Provider) - Appコンポーネントの定義
App.js
で、react-redux/connect()
を使って接続する
let rootElement = document.getElementById('root');
React.render(
// The child must be wrapped in a function
// to work around an issue in React 0.13.
<Provider store={store}>
{() => <App />}
</Provider>,
rootElement
);
// Wrap the component to inject dispatch and state into it
export default connect(select)(App);
上記のconnectでApp.props
にstore.dispatch
が追加になり、App.state
とstore.getState()
がバインドされる。つまり、reduxのstoreの変更がApp.state
の変更になる?
react-reduxのリポジトリを見る
import { Component } from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import { increment } from '../actionsCreators';
// Which part of the Redux global state does our component want to receive as props?
function mapStateToProps(state) {
return {
value: state.counter
};
}
// Which action creators does it want to receive by props?
function mapDispatchToProps(dispatch) {
return {
onIncrement: () => dispatch(increment())
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
react-redux/connect()
の引数にReduxのグローバルな状態とコンポーネントのプロパティをマップする関数と、コンポーネントのプロパティをReduxのActionにマップする関数がある。Counter.value
にstate.counter
を、Counter.onIncrement
にdispatch(increment()
をバインドしてる、ということか。ということは、App.js
のselect
も大事。
// Which props do we want to inject, given the global state?
// Note: use https://github.com/faassen/reselect for better performance.
function select(state) {
return {
visibleTodos: selectTodos(state.todos, state.visibilityFilter),
visibilityFilter: state.visibilityFilter
};
}
App.visibleTodos
にselectTodos()の結果
を、App.visibilityFilter
にstate.visibilityFilter
を割り当てる。
redux#bindActionCreators()
はActionを返すメソッド群をそれぞれdispatch()
でラップし、さらに1つのオブジェクトにまとめあげるユーティリティ。
2日目で学んだこと
- Reactのチュートリアルはわかりやすい
- reduxのstoreとReactのコンポーネントのバインディング方法もわかった
- ボイラープレートベースでいいから簡単なアプリを作成したい