過去にブクマしてたFlux関連の記事を一気読みする機会があったので、その流れで今までノータッチ状態だったReact.jsもさわってみました。 りぃさんのCodeGridの記事(これから始めるReact.js 全6回 | CodeGrid)が一段落ついたので、これを見つつアレンジしつつやってみます。
環境構築
今回作ったやつ
環境構築周りの説明が省かれててリポジトリ見るとシェルベースでコンパイルする感じになってたので、おれおれテンプレされがちであろうgulpベースで整えてみる。
package.json
{
"name": "react_study",
"version": "1.0.0",
"devDependencies": {
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.5.0",
"babelify": "7.3.0",
"browser-sync": "2.13.0",
"browserify": "13.0.1",
"gulp": "3.9.1",
"react": "15.1.0",
"react-dom": "15.1.0",
"run-sequence": "1.2.1",
"vinyl-source-stream": "1.1.0"
}
}
ES2015 と Browserifyを採用。
.babelrc
{
"presets": [
"es2015",
"react"
]
}
gulpfile.js
const gulp = require('gulp');
const browserify = require('browserify');
const babelify = require('babelify');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const browserSync = require('browser-sync');
const runSequence = require('run-sequence');
const proj = ['01', '02', '03', '04', '05', '06', '07'];
const defineProjTask = (name, task) => {
const tasks = [];
const defineTask = (dir) => {
const taskName = name + ':'+dir;
tasks.push(taskName);
gulp.task(taskName, task(dir));
};
proj.forEach(defineTask);
gulp.task(name, tasks);
};
defineProjTask('es:convert', (dir) => {
return (cb) => {
return browserify(dir + '/main.js', { debug: true })
// .babelrcを書かない場合は以下のようにする
// .transform(babelify,{presets: ["react","es2015"]})
.transform(babelify)
.bundle()
.on("error", function (err) { console.log("Error : " + err.message); })
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(gulp.dest(dir));
};
});
defineProjTask('watch', (dir) => {
return (cb) => {
gulp.watch([dir + '/**/*.jsx', dir + '/main.js', dir + '/*/*.js'], () => {
runSequence('es:convert:' + dir, browserSync.reload);
})
};
});
gulp.task(
'serve', () => {
browserSync({
server: {
baseDir: '.'
}
});
}
);
gulp.task('default', () => {
runSequence(
'es:convert',
'serve',
'watch'
);
});
サブディレクトリ単位でbrowserifyする感じで。これでgulpと打てば、コンパイルとソースの変更監視と画面が起動する。
一番簡単なサンプル
/01/index.html
<!DOCTYPE html>
<html>
<head lang="ja">
<meta charset="UTF-8">
</head>
<body>
<div id="container"></div>
<script src="bundle.js"></script>
</body>
</html>
/01/main.js
import React from "react";
import ReactDOM from "react-dom";
import Message from "./message.jsx";
import App from "./app.jsx";
ReactDOM.render(
<App />,
document.getElementById('container')
);
/01/app.jsx
import React from "react";
import ReactDOM from "react-dom";
import Message from "./message.jsx";
export default class App extends React.Component{
render (){
return (
<div>
<h1>簡単なサンプル</h1>
<Message />
</div>
);
}
}
継承の記述がJavaっぽくて良い感じ。ちなみにimport文で{}つければ以下のようも書ける。
import {Component} from "react";
export default class App extends Component{
// ...
}
/01/message.jsx
import React from "react";
export default class Message extends React.Component {
render() {
return <div>おす、おらごくう</div>;
}
}
これで以下のようになる。
propsとstate
- props
- コンポーネント外から渡された、読込専用の値
- state
- コンポーネント内で保持できる、変化する値
React.jsでは「各コンポーネントは極力stateを保持せずに、できる限りpropsでデータを受け取るべき」というポリシーがある。
propsで表示
/02/app.jsx
import React from "react";
import Message from "./message.jsx";
export default class App extends React.Component{
render (){
return (
<div>
<h1>propsでメッセージを表示する</h1>
<Message text="おまえはもう死んでいる"/>
</div>
);
}
}
/02/message.jsx
import React from "react";
export default class Message extends React.Component {
render() {
const { text } = this.props;
return <div>{text}</div>;
}
}
値の受け取り方が独特。こうなる。
stateで表示
/03/app.jsx
import React from "react";
import Message from "./message.jsx";
export default class App extends React.Component{
constructor() {
super();
this.state = {
val: 'やっほー'
};
this.changeMessage = this.changeMessage.bind(this);
}
changeMessage(ev) {
this.setState({ val: ev.target.value });
}
render (){
let { val } = this.state;
return (
<div>
<h1>stateでメッセージを表示する</h1>
<input type="text" onChange={this.changeMessage} value={val}/>
<Message text={val}/>
</div>
);
}
}
super()でスーパークラスのコンストラクタを実行しとかないとエラーがでるっぽい。 setState()でstateの変更を行う。 こうなる。
onClickとかのイベントについては以下参照。
JSX記法
これ覚えるのが一番めんどくさそう。
とりあえず {} で囲むとJSが書けるらしい
<div id="answer">
{ (bool) ? 'YES' : 'NO' }
</div>
複雑なやつは外側で書く。
render() {
let el;
if (bool) {
el = <div>YES</div>;
} else {
el = <span>NO</span>;
}
return (
<div>
{el}
</div>
);
}
変換されると以下のようになる。
React.createElement(
'div',
{ id: 'answer' },
(bool) ? 'YES' : 'NO'
)
2語以上の属性値名はキャメルケースで書く
- tabindex → tabIndex
- maxlength → maxLength
あと、ループ処理とkey propsとか...CodeGridみる
TODOアプリ
04/app.jsx
import React from "react";
import AddTodo from "./add_todo.jsx";
import TodoList from "./todo_list.jsx";
export default class App extends React.Component{
constructor() {
super();
this.state = {
todo: '働く',
todoList: ['寝る','起きる','休む','食べる']
};
this._onChange = this._onChange.bind(this);
this._onAdd = this._onAdd.bind(this);
this._onClose = this._onClose.bind(this);
}
_onChange(ev) {
this.setState({ todo: ev.target.value });
}
_onAdd(ev) {
ev.preventDefault();
const todo = this.state.todo;
if( !todo ) return;
const todoList = this.state.todoList.slice();
todoList.push(todo);
this.setState({ todoList: todoList, todo:''});
}
_onClose(ev, idx){
ev.preventDefault();
const todoList = this.state.todoList.filter((item, _idx)=>{
return _idx !== idx;
});
this.setState({ todoList: todoList});
}
render (){
let {todo, todoList} = this.state;
return (
<div>
<h1>React TODO</h1>
<AddTodo onChange={this._onChange} onAdd={this._onAdd} value={todo}/>
<TodoList list={todoList} onClose={this._onClose}/>
</div>
);
}
}
あまりコンポーネントで持たないほうが望ましいというstateはルートコンポーネントで管理。 stateを操作する各種イベントのコールバックもルートで管理。 一見 onChangeが不要そうだけど、これないとinput要素がreadOnlyになってしまう。
04/add_todo.jsx
import React from "react";
export default class AddTodo extends React.Component {
render() {
const {
onChange,
onAdd,
value
} = this.props;
return (
<form onSubmit={onAdd}>
<input type="text" onChange={onChange} value={value}/>
<input type="submit" value="add"/>
</form>
);
}
}
AddTodo.propTypes = {
onChange: React.PropTypes.func.isRequired,
onAdd: React.PropTypes.func.isRequired,
value: React.PropTypes.string.isRequired
};
TODO追加用の入力フィールドを定義したコンポーネント。 AddTodo.propTypesはpropsの型チェックを実行時に行ってくれるもの。詳細は以下。
04/todo_list.jsx
import React from "react";
export default class TodoList extends React.Component {
render() {
const {
list,
onClose
} = this.props;
return (
<ul>
{ list.map((d, idx) => {
return <li key={idx}><input type="checkbox" onClick={(ev)=>{onClose(ev, idx)}}/>{d}</li>
}) }
</ul>
);
}
}
TodoList.propTypes = {
list: React.PropTypes.arrayOf(React.PropTypes.string),
onClose: React.PropTypes.func.isRequired
};
TODO一覧。
次はりぃさんの新連載、Flux編を見ながらやってみたい。