ReactでTodoAppを作る
1:開発準備
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React Todo App</title> <link rel="stylesheet" href="css/styles.css" /> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- Don't use this in production: --> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="root"></div> <!-- テキストのタイプがバベルになっていることに注意! --> <script type="text/babel"> (() => { class App extends React.Component { render() { return ( <div> <h1>My Todos</h1> </div> ); } } ReactDOM.render(<App />, document.getElementById("root")); })(); </script> </body> </html>
TodoItemコンポーネントを作って、拡張しやすいようにしていきます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React Todo App</title> <link rel="stylesheet" href="css/styles.css" /> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- Don't use this in production: --> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="root"></div> <!-- テキストのタイプがバベルになっていることに注意! --> <script type="text/babel"> (() => { const todos = [ { id: 0, title: "Task 0", inDone: false }, { id: 1, title: "Task 1", inDone: false }, { id: 2, title: "Task 2", inDone: true }, ]; // TodoItem コンポーネントを作っていってあげます // 下の方で渡されたこちらの todo のデータが props の todo として渡ってくるので、中身は props.todo.title としてあげれば OK function TodoItem(props) { return( <li> {props.todo.title} </li> ); } // 関数でコンポーネントを作る function TodoList(props) { const todos = props.todos.map((todo) => { return ( // key が必要なのでこちらには todo.id としつつ、 todo のデータは todo 属性で渡す <TodoItem key = {todo.id} todo = { todo } /> ); }); return <ul>{todos}</ul>; } class App extends React.Component { constructor() { // superはコンストラクターに必ず書くことになっている super(); this.state = { // Todoの値をセットしてる todos: todos, }; } render() { return ( <div> <h1>My Todos</h1> // コンポーネントにプロップスを入れる <TodoList todos={this.state.todos} /> </div> ); } } ReactDOM.render(<App />, document.getElementById("root")); })(); </script> </body> </html>
チェックボックスをつける
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React Todo App</title> <link rel="stylesheet" href="css/styles.css" /> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- Don't use this in production: --> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> (() => { const todos = [ { id: 0, title: "Task 0", inDone: false }, { id: 1, title: "Task 1", inDone: false }, { id: 2, title: "Task 2", inDone: true }, ]; function TodoItem(props) { return( // タイトルをクリックしたときに、チェックもされると良いかと思うので、全体を label タグで囲ってあげる。 input タグを書いていきましょう。checkbox としてあげつつ、それがチェックされているかどうかは checked 属性に true / false を渡してあげれば良いので、 props.todo.isDone をそのまま渡してあげれば OK // それから Developer Tools にエラーが出ています。こちらでは checked を変更するなら state を変更しないといけないので onChange で実装してね、と出ています。では onChange を設定してあげて、 App コンポーネントが持っている state を変更したいので、どんどんと親要素に処理をゆずっていきましょう。 <li> <label> <input type="checkbox" checked={props.todo.isDone} onChange={() => props.checkTodo(props.todo)} /> {props.todo.title} </label> </li> ); } // 関数でコンポーネントを作る function TodoList(props) { const todos = props.todos.map((todo) => { return ( // props の todo を props の checkTodo に渡すので、親要素である TodoItem に checkTodo 属性を作ってあげて、さらに親要素である App コンポーネントに渡してあげましょう。 <TodoItem key = {todo.id} todo = { todo } checkTodo={props.checkTodo} /> ); }); return <ul>{todos}</ul>; } class App extends React.Component { constructor() { super(); this.state = { todos: todos, }; // App コンポーネントのこちらで checkTodo 属性を作らなくてはいけないので、この辺りをちょっと整形しつつ checkTodo 属性を作ってあげて、 App コンポーネントの中では state を処理できるので this.checkTodo メソッドで実装していきましょう。その前に this に関する bind() をしておかないといけないので、 constructor() の方で処理しておきます。 this.checkTodo = this.checkTodo.bind(this); } render() { return ( <div> <h1>My Todos</h1> // コンポーネントにプロップスを入れる <TodoList todos={this.state.todos} checkTodo={this.checkTodo} /> </div> ); } } ReactDOM.render( <App />, document.getElementById("root") ); })(); </script> </body> </html>
▼さらにチェックボックスを付けたら打ち消し線がつくようにformとCSSを編集
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React Todo App</title> <link rel="stylesheet" href="css/styles.css" /> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- Don't use this in production: --> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> (() => { const todos = [ { id: 0, title: "Task 0", inDone: false }, { id: 1, title: "Task 1", inDone: false }, { id: 2, title: "Task 2", inDone: true }, ]; function TodoItem(props) { return( // チェックがついたら打ち消し線がつくようにする<span>で設定し、CSSに記述 <li> <label> <input type="checkbox" checked={props.todo.isDone} onChange={() => props.checkTodo(props.todo)} /> <span className={props.todo.isDone ? 'done' : ''}> {props.todo.title} </span> </label> </li> ); } function TodoList(props) { const todos = props.todos.map((todo) => { return ( <TodoItem key = {todo.id} todo = { todo } checkTodo={props.checkTodo} /> ); }); return <ul>{todos}</ul>; } class App extends React.Component { constructor() { super(); this.state = { todos: todos }; this.checkTodo = this.checkTodo.bind(this); } // checkTodoメソッドを実装していく // ①todo が渡されるので引数に与えておいてあげる // ②state の値を変更したいのですが直接変更できないので、いったん todos のコピーを作ってあげる // ③オブジェクトのコピーに関しては map() を使ってあげれば良い // ④ひとつひとつの要素を todo としつつ、全ての props の値を持つコピーを作りたいので、 id に関しては todo.id 、 title は todo.title 、 isDone は todo.isDone としてあげればコピーが作られるはず checkTodo(todo) { const todos = this.state.todos.map(todo =>{ return {id: todo.id, title:todo.title, isDone: todo.isDone}; }); // ⑤コピーができたので、この中で何番目の要素の isDone を変更すれば良いか調べてあげる // ⑥まずは const pos (position) としてあげる // ⑦その後に state.todos の id プロパティの値のみの配列を、まずは map() で作ってあげる // ⑧その後に indexOf() で渡されてきた todo.id と同じものが何番目かを調べてあげる const pos = this.state.todos.map(todo => { return todo.id; }).indexOf(todo.id); // ⑨ここまでできたら todos の pos 番目の isDone を反転させてあげれば OK todos[pos].isDone = !todos[pos].isDone; // そこまでできたら新しく作った todos で setState() をしてあげれば良い this.setState({ todos:todos }) } render() { return ( <div> <h1>My Todos</h1> // コンポーネントにプロップスを入れる <TodoList todos={this.state.todos} checkTodo={this.checkTodo} /> </div> ); } } ReactDOM.render( <App />, document.getElementById('root') ); })(); </script> </body> </html>
.done { text-decoration: line-through; color: #ccc; }
スタイルを整える
render() { return ( <div className="container"> <h1>My Todos</h1> // コンポーネントにプロップスを入れる <TodoList todos={this.state.todos} checkTodo={this.checkTodo} /> </div> ); } }
body{ font-size: 16px; font-family: Vardana, sanserif; } .container{ width: 300px; margin: auto; } .container h1{ font-size: 16px; border-bottom: 1px solid #ddd; padding: 16px 0; } .container ul{ padding: 0; list-style: none; } .container li{ line-height: 1.5; } /* チェックボックスと文字の間に余白をつける */ .container input[type="checkbox"]{ margin-right: 5px; } .done { text-decoration: line-through; color: #ccc; }
deleteTodo() メソッドを実装
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>React Todo App</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> <div id="root"></div> <script type="text/babel"> (() => { const todos = [ {id: 0, title: 'Task 0', isDone: false}, {id: 1, title: 'Task 1', isDone: false}, {id: 2, title: 'Task 2', isDone: true} ]; 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)}>[x]</span> </li> ); } function TodoList(props) { const todos = props.todos.map(todo => { return ( <TodoItem key={todo.id} todo={todo} checkTodo={props.checkTodo} deleteTodo={props.deleteTodo} /> ); }); return ( <ul> {/* もし props.todos の length が true だったら todos にしてあげて、 false だったら何もないということなので Nothing to do! と表示してあげたい */} {props.todos.length ? todos : <li>Nothing to do!</li>} </ul> ); } class App extends React.Component { constructor() { super(); this.state = { todos: todos }; this.checkTodo = this.checkTodo.bind(this); this.deleteTodo = this.deleteTodo.bind(this); } deleteTodo(todo) { //⑦削除する前に一応 confirm() で聞いてあげると親切。もし confirm() が false だったら return としてあげて、以降の処理を行わないようにしてあげる if (!confirm('are you sure?')) { return; } //①state は直接いじれないので、まずはコピーを作る //②今回はオブジェクトのプロパティまではいじらないので、普通に slice() を使えば OK //③その後に何番目の要素を削除したいかを知りたいので pos (position) としてあげて、 this.state.todos.indexOf(todo) としてあげれば、同じ todo が何番目かがわかる const todos = this.state.todos.slice(); const pos = this.state.todos.indexOf(todo); //④要素を削除すればいいので splice() を使ってあげる //⑤では pos 番目から 1 つを削除するとしてあげる //⑥そこまでできたら新しい todos で todos をセットしてあげれば OK 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 }); } render() { return ( <div className="container"> <h1>My Todos</h1> <TodoList todos={this.state.todos} checkTodo={this.checkTodo} deleteTodo={this.deleteTodo} /> </div> ); } } ReactDOM.render( <App/>, document.getElementById('root') ); })(); </script> </body> </html>
フォームに値を入れられるようにする
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>React Todo App</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> <div id="root"></div> <script type="text/babel"> (() => { const todos = [ {id: 0, title: 'Task 0', isDone: false}, {id: 1, title: 'Task 1', isDone: false}, {id: 2, title: 'Task 2', isDone: true} ]; 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)}>[x]</span> </li> ); } function TodoList(props) { 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>Nothing to do!</li>} </ul> ); } //③item 属性で渡された値は、こちらの関数の props 引数で渡されるので、それをそのまま input type="text" に反映させてあげれば OK。では value 属性に対して props.item としてあげる。 //④React では UIの変更は setState() でのみ行うようになっているため、onChange イベントをこちらに設定してあげて、 App コンポーネントの方で実装する。 //⑤親要素に渡していってあげればいいので props.updateItem としつつ、 App コンポーネントの中で TodoForm に対して updateItem 属性を付けてあげれば OK function TodoForm(props) { return ( <form> <input type="text" value={props.item} onChange={props.updateItem}/> <input type="submit" value="Add"/> </form> ); } //① form から入力されたデータ、アプリのデータは state で一元管理しなくてはいけないから、この state に item という名前で保持してあげる。最初は空文字で良い。 class App extends React.Component { constructor() { super(); this.state = { todos: todos, item: '' }; //⑦this の bind() を constructor() の方で行ってあげる。 this.checkTodo = this.checkTodo.bind(this); this.deleteTodo = this.deleteTodo.bind(this); this.updateItem = this.updateItem.bind(this); } deleteTodo(todo) { if (!confirm('are you sure?')) { return; } const todos = this.state.todos.slice(); const pos = this.state.todos.indexOf(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 }); } //⑧form の値はイベントオブジェクトから取得できるので、 e を引数にしつつ this.setState() としてあげて、 state の中の item は form の target.value だよ、としてあげると form に入力された値が UI に反映される updateItem(e) { this.setState({ item: e.target.value }); } render() { return ( <div className="container"> <h1>My Todos</h1> <TodoList todos={this.state.todos} checkTodo={this.checkTodo} deleteTodo={this.deleteTodo} /> {/* ②その後に TodoForm に対してこちらのデータを渡してあげたいので、 item 属性で this.state.item を渡してあげる*/} {/* ⑥親要素に渡していってあげればいいので props.updateItem としつつ、 App コンポーネントの中で TodoForm に対して updateItem 属性を付けてあげれば OK。実装の方は this.updateItem でしてあげる*/} <TodoForm item={this.state.item} updateItem={this.updateItem} /> </div> ); } } ReactDOM.render( <App/>, document.getElementById('root') ); })(); </script> </body> </html>
Todoを追加するようにする
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>React Todo App</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> <div id="root"></div> <script type="text/babel"> (() => { const todos = [ {id: 0, title: 'Task 0', isDone: false}, {id: 1, title: 'Task 1', isDone: false}, {id: 2, title: 'Task 2', isDone: true} ]; 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)}>[x]</span> </li> ); } function TodoList(props) { 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>Nothing to do!</li>} </ul> ); } //①form に対して onSubmit を付けてあげる。onSubmit 属性を付けてあげて、処理自体は App コンポーネントで行いたいので props.addTodo としてあげつつ、 App コンポーネントの方で addTodo 属性を追加してあげれば OK。 function TodoForm(props) { return ( <form onSubmit={props.addTodo}> <input type="text" value={props.item} onChange={props.updateItem}/> <input type="submit" value="Add"/> </form> ); } //③bind() が必要なのでこちらで設定してあげ class App extends React.Component { constructor() { super(); this.state = { todos: todos, item: '' }; this.checkTodo = this.checkTodo.bind(this); this.deleteTodo = this.deleteTodo.bind(this); this.updateItem = this.updateItem.bind(this); this.addTodo = this.addTodo.bind(this); } //④まず画面が遷移しないようにしたいのでイベントオブジェクトを渡しつつ、 preventDefault() としてあげると submit してもページが遷移しない。あとは新しい要素を追加したいのですが内容は後で作ることにして、とりあえず空のオブジェクトを用意してあげる //⑤その後の処理ですが、この item を既存の todos に追加したいので state をいじるにあたって、直接変更できないのでまずは todos のコピーを作ってあげる。とりあえず slice() してあげて、その後に todos に対して item を push() して追加してあげる。その後に新しい todos を todos にセットすれば良いので、ちょっとややこしいのですがこのようにしてあげれば OK 。ths addTodo(e) { e.preventDefault(); const item = { }; const todos = this.state.todos.slice(); todos.push(item); this.setState({ todos: todos }); } deleteTodo(todo) { if (!confirm('are you sure?')) { return; } const todos = this.state.todos.slice(); const pos = this.state.todos.indexOf(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 }); } updateItem(e) { this.setState({ item: e.target.value }); } render() { return ( <div className="container"> <h1>My Todos</h1> <TodoList todos={this.state.todos} checkTodo={this.checkTodo} deleteTodo={this.deleteTodo} /> <!--②this.addTodo としてあげて、実装自体は App コンポーネントの addTodo() メソッドで行なっていく--> <TodoForm item={this.state.item} updateItem={this.updateItem} addTodo={this.addTodo} /> </div> ); } } ReactDOM.render( <App/>, document.getElementById('root') ); })(); </script> </body> </html>
ユニークのIDを作成する
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>React Todo App</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> <div id="root"></div> <script type="text/babel"> (() => { const todos = []; 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)}>[x]</span> </li> ); } function TodoList(props) { 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>Nothing to do!</li>} </ul> ); } function TodoForm(props) { return ( <form onSubmit={props.addTodo}> <input type="text" value={props.item} onChange={props.updateItem}/> <input type="submit" value="Add"/> </form> ); } //②id ですが何らかのユニークな値を付けなくてはいけない。いろいろな実装方法がありますが、今回は簡易的にかぶらなそうな文字列を生成して、それを使ってあげる。では getUniqueId() という関数を作ってあげましょう。 //③今回ですが適当に時刻と乱数を 36 進数にしてつなげてあげれば、それっぽい文字列でかぶらない id が生成されるはず。では時刻と乱数を - (ハイフン)で結んであげる。 //④それから初期データはもう要らないので、ここはざっと削ってしまいましょう。 //⑤ちゃんと追加できていて Developer Tools のReactのCompornentsで見てあげるとちゃんとIDもう追加されているのがわかる function getUniqueId() { return new Date().getTime().toString(36) + '-' + Math.random().toString(36); } class App extends React.Component { constructor() { super(); this.state = { todos: todos, item: '' }; this.checkTodo = this.checkTodo.bind(this); this.deleteTodo = this.deleteTodo.bind(this); this.updateItem = this.updateItem.bind(this); this.addTodo = this.addTodo.bind(this); } addTodo(e) { e.preventDefault(); //⑦ただ、空の文字列も追加されてしまうので、その辺りも直しておきましょう。addTodo() で、もし item を trim() したときに、それが空文字だったらそれ以降の処理をしないため return としてあげれば OK 。では空文字が追加できるかどうかを見てあげましょう…。 if (this.state.item.trim() === '') { return; } //①まずitem を作っていく。まず title ですが、 this.state.item にすれば OK 。isDone は最初 false にすれば良い。 const item = { id: getUniqueId(), title: this.state.item, isDone: false }; //⑥ただ、入力した後にこちらの form の中身をクリアしてあげるとさらに良い。addTodo() したときに item に対して空文字をセットすれば良いのでこれで OK。 const todos = this.state.todos.slice(); todos.push(item); this.setState({ todos: todos, item: '' }); } deleteTodo(todo) { if (!confirm('are you sure?')) { return; } const todos = this.state.todos.slice(); const pos = this.state.todos.indexOf(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 }); } updateItem(e) { this.setState({ item: e.target.value }); } render() { return ( <div className="container"> <h1>My Todos</h1> <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>
Headerタグをつける(Todo の総数を表示できるようにする)
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>React Todo App</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> <div id="root"></div> <script type="text/babel"> (() => { const todos = []; //③ TodoHeader コンポーネントを上の方に作っておくとわかりやすいかと思います。todos が渡されてくるので props で受け取ってあげましょう。 //④まずは見出しが必要なので h1 タグを書いてあげて、タイトルは My Todos としてあげましょう。 //⑤とりあえずダミーで (1/3) としてあげて、表示を確認。 function TodoHeader(props) { return ( <h1> My Todos <span>(1/3)</span> </h1> ); } 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)}>[x]</span> </li> ); } function TodoList(props) { 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>Nothing to do!</li>} </ul> ); } function TodoForm(props) { return ( <form onSubmit={props.addTodo}> <input type="text" value={props.item} onChange={props.updateItem}/> <input type="submit" value="Add"/> </form> ); } function getUniqueId() { return new Date().getTime().toString(36) + '-' + Math.random().toString(36); } class App extends React.Component { constructor() { super(); this.state = { todos: todos, item: '' }; this.checkTodo = this.checkTodo.bind(this); this.deleteTodo = this.deleteTodo.bind(this); this.updateItem = this.updateItem.bind(this); this.addTodo = this.addTodo.bind(this); } addTodo(e) { e.preventDefault(); if (this.state.item.trim() === '') { return; } const item = { id: getUniqueId(), title: this.state.item, isDone: false }; const todos = this.state.todos.slice(); todos.push(item); this.setState({ todos: todos, item: '' }); } deleteTodo(todo) { if (!confirm('are you sure?')) { return; } const todos = this.state.todos.slice(); const pos = this.state.todos.indexOf(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 }); } updateItem(e) { this.setState({ item: e.target.value }); } //①まずはこちらの見出しもコンポーネントにしてしまいましょう。TodoHeader としてあげます。 //②Todo の数を表示したいので todo 属性でデータを渡してあげましょう。 render() { return ( <div className="container"> <TodoHeader todos={this.state.todos} /> <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>
表示はされているのですがスタイルを変えたいので CSS で設定
・h1 タグの中の span 要素に対していろいろ設定 ・文字を薄くしたいのと、文字を小さくしたいのと、あとは太字ではなくしたいので font-weight: normal; ・左側が詰まっていると見づらいので margin-left を付けてあげる
h1 > span { color: #ccc; font-size: 12px; font-weight: normal; margin-left: 5px; }