Single Page Application を Angular で実装しようと思っていたんだけど、 Angular2 でコンポーネント指向にガラリと変わるみたいなので、 Angular の採用は見送って React を選択。 同じコンポーネント指向なら React でいいかな、と。
React 入門ということで TODO リストのサンプルを書いてみる。 毎度のこだわりとして、その場編集機能を実装している。
コピペするだけで試せるようにしたかったので、あえて事前にコンパイルする方法は取ってない。 おかげで画面が表示されるまで結構待たされるけど、手軽さ優先で我慢我慢。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Tutorial</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> </head> <body> <div id="content"></div> <script type="text/babel"> // タスクを表示するコンポーネント var Task = React.createClass({ handleEdit(e) { this.props.onTaskSave(this.props.task, { editing: true }); }, handleDelete(e) { this.props.onTaskDelete(this.props.task); }, handleChangeCheckbox(e) { this.props.onTaskSave(this.props.task, { completed: !this.props.task.completed }); }, render() { // JSX の中で分岐する場合は即時関数を使う return ( <div> <input type="checkbox" checked={this.props.task.completed} onChange={this.handleChangeCheckbox} /> {(() => { if (this.props.task.completed) { return <del>{this.props.task.name}</del> } else { return <span>{this.props.task.name}</span> } }())} <a href="#" onClick={this.handleEdit}>[edit]</a> <a href="#" onClick={this.handleDelete}>[del]</a> </div> ); } }); // タスクをその場編集するためのコンポーネント var EditTaskForm = React.createClass({ getInitialState() { return { name: "" }; }, handleCancel(e) { this.props.onTaskSave(this.props.task, { editing: false }); }, handleSave(e) { e.preventDefault(); if (!this.state.name) { return; } this.props.onTaskSave(this.props.task, { name: this.state.name, editing: false }); this.setState({ name: "" }); }, handleChangeText(e) { this.setState({ name: e.target.value }); }, render() { return ( <div> <input type="text" value={this.state.name || this.props.task.name} onChange={this.handleChangeText} ref="name"/> <button onClick={this.handleSave}>save</button> <button onClick={this.handleCancel}>cancel</button> </div> ); } }); // タスク一覧を表示するためのコンポーネント var TaskList = React.createClass({ render() { var rows = this.props.tasks.map(t => { if (t.editing) { return ( <EditTaskForm task={t} onTaskSave={this.props.onTaskSave} /> ); } else { return ( <Task task={t} onTaskSave={this.props.onTaskSave} onTaskDelete={this.props.onTaskDelete} /> ); } }); return ( <div> {rows} </div> ); } }); // タスク登録フォームを表示するためのコンポーネント var AddTaskForm = React.createClass({ handleAdd(e) { e.preventDefault(); var newTask = ReactDOM.findDOMNode(this.refs.name).value.trim(); if (!newTask) { return; } this.props.onTaskAdd({ name: newTask }); ReactDOM.findDOMNode(this.refs.name).value = ""; }, render() { return ( <div> <input type="text" placeholder="newTask" ref="name"/> <button onClick={this.handleAdd}>add</button> </div> ); } }); // ルートコンポーネント // 状態をまとめて管理する var TaskApp = React.createClass({ getInitialState() { return { tasks: [ { name: "foo", completed: false, editing: false }, { name: "bar", completed: false, editing: false }, { name: "hoge", completed: false, editing: false }, { name: "fuga", completed: false, editing: false } ] }; }, handleTaskAdd(newTask) { var newTasks = this.state.tasks.concat(newTask); this.setState({ tasks: newTasks }); }, handleTaskDelete(task) { var newTasks = this.state.tasks.filter(t => { return t !== task; }); this.setState({ tasks: newTasks }); }, handleTaskSave(task, newValues) { var newTasks = this.state.tasks.map(t => { if (t === task) { return $.extend(task, newValues); } else { return t; } }); this.setState({ tasks: newTasks }); }, render() { return ( <div> <AddTaskForm onTaskAdd={this.handleTaskAdd} /> <TaskList tasks={this.state.tasks} onTaskSave={this.handleTaskSave} onTaskDelete={this.handleTaskDelete} /> </div> ); } }); ReactDOM.render( <TaskApp />, document.getElementById("content") ); </script> </body> </html>
ルートにだけ状態を持たせる作りに挑戦したけど、イベントの伝播が面倒だった。 この程度のサンプルで面倒に感じるんだから、もっと本格的なアプリになったら、 やっぱりフレームワークは必要だな。 Redux あたりを試してみようかな。