とりあえず react に触れてみたかった。
ググってみると、nodejs のinstallから紹介しているサイトを多く見かけますが、 ブラウザ + エディタだけで気軽に試すことができる dotinstall.com にて写経。
以下は、その成果物のTODO管理。読めば、分かると思います
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>reactjs.org の練習 at dotinstall.com </title> <link rel="stylesheet" href="css/styles.css"> <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> </head> <body> <!-- #root 以下を Reactが制御/描画します --> <div id="root"></div> <script type="text/babel"> (() => { function TodoHeader(props) { // filter()で未完了のTODOを抽出 const remaining = props.todos.filter(todo => { return !todo.isDone; }); return ( <h1> My Todos <span>({remaining.length}/{props.todos.length})</span> <button onClick={props.purge}>完了TODOの一括消去</button> </h1> ); } function TodoList(props) { // map() で、TODOを <TodoItem / > Component に変換 const todos = props.todos.map(todo => { return ( <TodoItem key={todo.id} todo={todo} checkTodo={props.checkTodo} deleteTodo={props.deleteTodo} /> ); }); return ( <ul> {props.todos.length ? todos : <li>対応すべきTODOはありません!</li>} </ul> ); } function TodoItem(props) { return ( <li key={props.todo.id}> <label> <input type="checkbox" checked={props.todo.isDone} onChange={() => props.checkTodo(props.todo)} /> <span className={props.todo.isDone ? 'done' : ''}> {props.todo.title} </span> </label> <span className="cmd" onClick={() => props.deleteTodo(props.todo)}>[×]</span> </li> ); } function TodoForm(props) { return ( <form onSubmit={props.addTodo}> <input type="text" value={props.item} onChange={props.updateItem}/> <input type="submit" value="追加"/> </form> ); } function getUniqueId() { return new Date().getTime().toString(36) + '-' + Math.random().toString(36); } class App extends React.Component { constructor() { super(); // Reactの作法的に、stateは1箇所のcomponentで管理するらしい this.state = { todos: [], item: '' // 新たに追加するTODOの内容 }; this.checkTodo = this.checkTodo.bind(this); this.deleteTodo = this.deleteTodo.bind(this); this.updateItem = this.updateItem.bind(this); this.addTodo = this.addTodo.bind(this); this.purge = this.purge.bind(this); } purge() { if ( ! confirm('完了TODOを一括消去してよろしいですか ?')) { return; } // filter() で未完了TODOを集め、 setState() にて更新 const todos = this.state.todos.filter(todo => { return ! todo.isDone; }); this.setState({ todos: todos }); } addTodo(e) { // onSubmit()で呼ばれる為、画面遷移されることを防ぎます e.preventDefault(); if (this.state.item.trim() === '') { return; } const item = { id: getUniqueId(), title: this.state.item, isDone: false }; // slice()にて、元の配列をshallow copy const todos = this.state.todos.slice(); todos.push(item); this.setState({ todos: todos, item: '' }); } deleteTodo(todo) { if (!confirm('削除してよろしいですか ?')) { return; } // slice()にて、元の配列をshallow copyし // indexOf()にて削除対象の位置を探索 const todos = this.state.todos.slice(); const pos = this.state.todos.indexOf(todo); // splice()にて、該当のTODOを削除 todos.splice(pos, 1); this.setState({ todos: todos }); } checkTodo(todo) { const todos = this.state.todos.map(todo => { return {id: todo.id, title: todo.title, isDone: todo.isDone}; }); const pos = this.state.todos.map(todo => { return todo.id; }).indexOf(todo.id); todos[pos].isDone = !todos[pos].isDone; this.setState({ todos: todos }); } // TodoForm componentの onChange()から呼ばれます updateItem(e) { this.setState({ item: e.target.value }); } // componentDidUpdate()は、Component の props や state が // 更新された際に呼ばれます. // ここでは、TODOの内容をjsonにして、localStorage へ保存しています componentDidUpdate() { localStorage.setItem('todos', JSON.stringify(this.state.todos)); } // componentDidMount()により、上記で json保存した内容を画面にloadしています componentDidMount() { this.setState({ todos: JSON.parse(localStorage.getItem('todos')) || [] }); } render() { return ( <div className="container"> <TodoHeader todos={this.state.todos} purge={this.purge} /> <TodoList todos={this.state.todos} checkTodo={this.checkTodo} deleteTodo={this.deleteTodo} /> <TodoForm item={this.state.item} updateItem={this.updateItem} addTodo={this.addTodo} /> </div> ); } } ReactDOM.render( <App/>, document.getElementById('root') ); })(); </script> </body> </html>
body { } .container { width: 400px; margin: auto; } .container h1 { border-bottom: 1px solid #ddd; padding: 16px 0; } .container ul { padding: 0; list-style: none; } .container li { line-height: 2; } .container input[type="checkbox"] { margin-right: 5px; } .cmd { cursor: pointer; color: #08c; margin-left: 5px; } .container input[type="text"] { padding: 2px; margin-right: 5px; } h1 > span { color: #ccc; font-weight: normal; margin-left: 5px; } h1 > button { float: right; } .done { text-decoration: line-through; color: #ccc; }