Reactを使ってTodoアプリを作ってみる_3日目
初めに
この記事はReact初心者が初めての自作アプリ作成を行う様子を書き残した日記です。
前回はForm
コンポーネントを作成し、タスク登録機能を実装しました。
今回は登録されたタスクを表示する機能の実装に着手します。
タスク表示機能
登録したタスクを表示する機能はTodoList
コンポーネントが担当します。
前回、Form
コンポーネントのstateに登録されたタスクを保存するようにしました。
今回はその情報をpropsを通じてTodoList
コンポーネント渡すようにします。
タスク追加機能の実装は以下となります。
TodoListコンポーネントの引数にtodos
を追加し、タスク一覧の情報を渡してTodoList
コンポーネント側に渡す流れになります。
src/App.js コンポーネント呼び出し部分
<React.Fragment> <h1>Todo App</h1> <Form value={this.state.value} handleChange={this.handleChange} handleClick={this.handleClick} /> <TodoList/> <TodoList todos={this.state.todos} /> </React.Fragment>
src/TodoList.js
render() { return ( <ul> {this.props.todos.map( (todo) => <li>{todo}</li> )} </ul> ); }
タスク一覧の表示にはJavaScripのmapメソッドを使用しています。
mapメソッドは引数に処理を渡すと、配列のすべての要素にその処理を実行し、その結果から得られた新しい配列を生成します。
つまり以下の処理はtodos
という配列から1つ要素を取りしてtodo
に格納し、そのtodo
に<li>タグを追加する、という処理を配列の1つ1つの要素に対して実行する処理となります。
this.props.todos.map((todo) => <li>{todo}</li>)
これでタスクがリスト化されて画面に表示されるようになりました。
タスク削除機能
次は登録されたタスクを削除する機能を追加します。
タスクの隣にDeleteボタンを追加し、そのボタンが押されるとそれに紐づくタスクが削除される、という処理を実装します。
さて、その機能を実装したソースは以下の通りとなります。
src/TodoList.js
import React from 'react' class TodoList extends React.Component { render() { return ( <ul> {this.props.todos.map( (todo, i) => <li key={i}><input type="checkbox" />{todo}<button onClick={() => this.props.handleDeleteClick(i)}>Delete</button> </li> )} </ul> ); } } export default TodoList;
src/App.js handleDeleteClickメソッド
handleDeleteClick = (index) => { const newTodo = this.state.todos.slice() newTodo.splice(index,1) this.setState({todos: newTodo}) }
src/App.js renderメソッド
render() { return ( <React.Fragment> <h1>Todo App</h1> <Form value={this.state.value} handleChange={this.handleChange} handleClick={this.handleClick} /> <TodoList todos={this.state.todos} /> <Form value={this.state.value} handleChange={this.handleChange} handleAddClick={this.handleAddClick} /> <TodoList todos={this.state.todos} handleDeleteClick={this.handleDeleteClick} /> </React.Fragment> ); }
handleDeleteClickメソッド
handleDeleteClick
メソッドはDeleteボタンがクリックされた時の動作を定義したメソッドです。
これをpropsを通じてTodoList
コンポーネントに渡すことで、App
コンポーネントで定義したメソッドをTodoList
コンポーネント側で使うことができます。
渡されたhandleDeleteClickApp
メソッドはAppコンポーネントのstateを参照するので、App
コンポーネントで管理しているstateの情報から対象の情報を削除する、といった処理が実現可能になります。
Key
また、クリックされた要素を特定するため、ReactのKey
という機能を使用しています。
Key
はどの要素が変更、追加、もしくは削除されたのかをReactが識別できるようにする機能です。
要素にkey
というキーワードを与えることで、その要素を一意に特定できるような情報を付与することができます。
this.props.todos.map((todo, i) => <li key={i}><input type="checkbox" />{todo}<button onClick={() => this.props.handleDeleteClick(i)}>Delete</button></li>)
今回はDeleteボタンが押されたのがどの要素なのか特定できるようにする必要があるため、mapによって出力された要素に降順の番号を付与しています。
Deleteボタンが押されたとき、handleDeleteClickメソッドにその番号を渡すことで配列内のどの要素を消すのか指定するような仕組みとなっています。
おわりに
今回はこれまでより複雑なロジックになってきました。 登録機能、削除機能を実装するために活用した以下のメソッド、機能をキチンと押さえておこうと思います。
- JavaScriptのmapメソッド
- ReactのKey
おまけ:ハマりポイント
実は削除機能を実装するにあたって長い間ハマっていたところがあるので、自戒の念を込めて書き残しておきます。
イベントハンドラに引数付きのメソッドを追加する
handleDeleteClick
メソッドをonClickイベントに登録する時、以下のように書くと予期せぬ挙動を取りました。
src/TodoList.js
<button onClick={this.props.handleDeleteClick(i)}>Delete</button>
タスクが追加されたタイミングでhandleDeleteClick
メソッドが呼ばれ、登録されたタスクが即座に消える、というバグが発生したのです。
このバグの原因ですが、this.props.handleDeleteClick(i)
という箇所がメソッドの登録ではなく、メソッドの実行としてReactに認識されているためです。
handleDeleteClick
メソッドは引数を必要とするメソッドなので、handleDeleteClick(i)
と書いたのですが、ここのカッコによりメソッドの実行であると判断されてしまったようです。
そのためタスクが描画された時にhandleDeleteClick
メソッドが実行されるというバグが発生してしまいました。
引数付きのメソッドをイベントハンドラに登録したいのであれば、メソッドの登録だと明示的にしてあげましょう。
その方法の1つがアロー関数による無名関数の定義です。
<button onClick={() => this.props.handleDeleteClick(i)}>Delete</button>
これによりthis.props.handleDeleteClick(i)
というメソッドが無名の関数定義として処理され、正しくイベントハンドラに登録されるようになります。
Reactを使ってTodoアプリを作ってみる_2日目
初めに
この記事はReact初心者が初めての自作アプリ作成を行う様子を書き残した日記です。
昨日はこれから作るアプリの部品を仮置きしてみて、イメージを固めるところまでやってみました。
今日はアプリの機能部分の実装に着手していこうと思います。
stateをAppコンポーネントに移譲する
個別のコンポーネントがいったん形になったので、コンポーネントの連携を行います。
まずはForm
コンポーネントで管理しているstate
をApp
コンポーネントに移譲します。
App
コンポーネントで状態管理を行うことで、アプリ全体の状態を各コンポーネントに共有できるようにします。
src/App.js
import React from 'react'; import Form from './Form'; import TodoList from './TodoList' class App extends React.Component { constructor(props) { super(props) this.state = {value: ''} this.handleChange = this.handleChange.bind(this) } handleChange(event) { this.setState({value: event.target.value}) } render() { return ( <React.Fragment> <h1>Todo App</h1> <Form value={this.value} handleChange={this.handleChange} /> <TodoList/> </React.Fragment> ); } } export default App;
src/Form.js
import React from 'react' class Form extends React.Component { render() { return ( <React.Fragment> <label> todo: <input type="text" className="todoform" value={this.props.value} onChange={this.props.handleChange} /> <input type="submit" value="Add" /> </label> </React.Fragment> ); } } export default Form;
タスク追加機能を実装する
このアプリではAddボタンが押された時にタスクを追加します。
その機能を実現するため、Addボタンのクリックを検知するイベントリスナーhandleClick
を定義しましょう。
handleClickメソッド
handleClick
はボタンが押された時、次の処理を実行します。
- リストに新しいタスクを追加する
- フォームに入力された値をクリアする
リストに追加する処理を実現するため、stateにtodos
を追加します。
todos
はタスクのリストを管理する配列で、ボタンがクリックされた時にフォームの値をこの配列に追加するようにします。
そしてhandleClick
でstateの各値を更新することで、上の2つの処理を実現させます。
これまでの話を取り込んだ結果が以下のコードとなります。
src/App.js
import React from 'react'; import Form from './Form'; import TodoList from './TodoList' class App extends React.Component { constructor(props) { super(props) this.state = {value: '' ,todos: []} this.handleChange = this.handleChange.bind(this) this.handleClick = this.handleClick.bind(this) } handleChange(event) { this.setState({value: event.target.value}) } handleClick() { const newTodo = this.state.todos.slice() newTodo.push(this.state.value) this.setState({value: '', todos: newTodo}) } render() { return ( <React.Fragment> <h1>Todo App</h1> <Form value={this.state.value} handleChange={this.handleChange} handleClick={this.handleClick} /> <TodoList/> </React.Fragment> ); } } export default App;
import React from 'react' class Form extends React.Component { render() { return ( <React.Fragment> <label> todo: <input type="text" className="todoform" value={this.props.value} onChange={this.props.handleChange} /> <input type="submit" value="Add" onClick={this.props.handleClick} /> </label> </React.Fragment> ); } } export default Form;
まとめ
これでForm
コンポーネント、App
コンポーネントでタスク登録機能を実装する準備が整いました。
明日はTodoListコンポーネントを修正し、登録されたタスクが表示されるようにしようと思います。
Reactを使ってTodoアプリを作ってみる_1日目
初めに
最近になってフロントエンド開発案件に配属されたのですが、実際に開発するとなるとさっぱり手が動かないということに気付かされました。
事前にチュートリアルを読んで勉強していたのですが、それはただ正解が用意されたコードをなぞっていただけのこと。
いざ開発するとなるとどうすればいいのかわからなくなってしまうのです。
そんなわけで実装力を鍛えようと思い、簡単なTodoアプリの作成をしてみることにしました。
環境構築
Create React Appを使ってさっくりプロジェクトを作成します。
なおCreate React Appを実行するには事前にNode.jsのインストールが必要です。
npx create-react-app react-todo cd react-todo npm start
プロジェクトの作成が正常に完了すれば下の画面が立ち上がります。
作成するアプリについて
まずは必要最低限の機能だけもったTodoアプリの作成を目指します。
今回実装するのは以下の機能です。
- タスクを登録する
- タスクを削除する
- タスクを完了状態にする
アプリの構成
以下の3つのコンポーネントでアプリを構築していきます。
Appコンポーネントはアプリ全体の状態を管理します。
FormコンポーネントはTodoを入力するフォームと登録のボタン部分を担当します。
TodoListコンポーネントは入力されたTodoをリスト化して表示する部分を担当します。
アプリの作成
まずはcreate-react-appで作成されたファイルを整理します。 create-react-appが生成するソースの内、使わないファイルは削除してしまいましょう。
今回はsrc
フォルダ配下のApp.js
、index.js
、serviceWorker.js
以外は削除します。
formコンポーネントの作成
まずは形から作成していきましょう。
このアプリのフォームを構成するForm
コンポーネントを定義し、入力フォームとそのフォームに入力されたタスクを登録するためのボタンを仮作成します。
src/Form.js
import React from 'react' class Form extends React.Component { constructor(props) { super(props) this.state = {value: ''} this.handleChange = this.handleChange.bind(this) } handleChange(event) { this.setState({value: event.target.value}) } render() { return ( <React.Fragment> <label> todo: <input type="text" className="todoform" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Add" /> </label> </React.Fragment> ); } } export default Form;
このForm
コンポーネントをApp
コンポーネント側で呼び出し、表示されるか確認してみます。
src/App.js
import React from 'react'; import Form from './Form'; function App() { return ( <React.Fragment> <h1>Todo App</h1> <Form/> </React.Fragment> ); } export default App;
無事、フォームとボタンが表示されました。
TodoListコンポーネントの作成
次は登録されたTodoを表示するリストを作成します。
こちらもまずは形から。 登録フォームはまだ機能していないので、静的なリストを仮置きしてみます。
src/TodoList.js
import React from 'react' class TodoList extends React.Component { render() { return ( <ul> <li><input type="checkbox" /> Temp 1 <input type="submit" value="delete" /></li> <li><input type="checkbox" /> Temp 2 <input type="submit" value="delete" /></li> <li><input type="checkbox" /> Temp 3 <input type="submit" value="delete" /></li> </ul> ); } } export default TodoList;
App.js
からTodoList
コンポーネントを呼び出し、正しく表示されるか確認してみます。
src/App.js
import React from 'react'; import Form from './Form'; import TodoList from './TodoList'; function App() { return ( <React.Fragment> <h1>Todo App</h1> <Form/> <TodoList/> </React.Fragment> ); } export default App;
無事表示されました。
まとめ
今日はとりあえず形だけ作成してみました。
明日は実際にタスクを登録する機能を追加するところまでやる予定です。
この程度の実装でしたが、躓いた所がいくつかありました。
特にthisのbindを忘れてhandleChange
メソッドが認識されなかった事が大きなハマりポイントでしたので、JavaScriptのthisについて勉強しなおさないといけませんね…。