今回はReactでTodoアプリを作成してみました。
create-react-app
を使用して、プロジェクトを作成した後に、Todoアプリを作成していきます。
create-react-app
はReact
のプロジェクトを簡単に作成してくれるツールです。
Reactのバージョン17.0.2
で下記のようなTodoアプリを作成してみました。
プロジェクトの立ち上げ方から、コードの解説までを記載しています。
create-react-appでプロジェクトを作成する
npxでcreate-react-app
を指定して、プロジェクトを作成します。
今回はreact_todo_app
という名前でプロジェクトを作成しました。
$ npx create-react-app react_todo_app
コマンドを実行すると、下記のようにプロジェクトが作成されます。
$ npx create-react-app react_todo_app
Creating a new React app in /Users/yasuaki/react_todo_app.
Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template...
//...省略...
cd react_todo_app
npm start
Happy hacking!
この状態で、上記に書かれているようにプロジェクトのディレクトリに移動して、npm start
を実行してみます。
そうすると、下記のように初期で用意されている画面が表示されます。
※ちなみにプロジェクトに大文字を含めると、下記のようにnpmの命名制限でエラーになります。
Cannot create a project named “reactTodoApp” because of npm naming restrictions:
* name can no longer contain capital letters
Please choose a different project name.
Todoコンポーネントを作成する
今回はTodo画面のコンポーネントを作って、初期で表示されているAppコンポーネントをTodoコンポーネントに切り替えます。
まずは、下記のようにTodo.css
とTodo.js
ファイルを追加します。
Todo.cssを作成する
Todo画面で使用するスタイルを定義します。
今回はBootstrap5を読み込むだけにしました。
Todo.css
を開いて、下記のようにBootstrap5を読み込むようにします。
@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css');
Todo.jsを作成する
Reactのコンポーネントの書き方には、クラスコンポーネントと関数コンポーネントが存在します。
今回は関数コンポーネントの書き方で、Todo.js
というファイルにTodo
というコンポーネントを作成しました。
import './Todo.css'
import React, { useState } from 'react'
export const Todo = () => {
const [todoText, setTodoText] = useState('')
const [todos, setTodoList] = useState([])
const handleAddTodo = () => {
if (todoText === '') {
alert('TODOを入力してください')
return
}
setTodoList([...todos, todoText ])
setTodoText('')
}
const handleChaneText = (e) => {
setTodoText(e.target.value)
}
const ShowTodoList = () => {
if (todos.length === 0) {
return ''
}
const handleDeleteTodo = (paramIndex) => {
const newTodos = todos.filter((data, index) => { return index !== paramIndex })
setTodoList([...newTodos])
}
const list = todos.map((data, index) => {
return (
<li key={index} className="list-group-item d-flex justify-content-between align-items-center">
{data}
<button className='btn btn-danger' onClick={() => handleDeleteTodo(index)}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
<path fillRule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
</svg>
</button>
</li>
)
})
return (
<ul className="list-group mt-3">
{list}
</ul>
)
}
return (
<div>
<nav className="navbar navbar-dark bg-dark">
<div className="container-fluid">
<span className="navbar-brand">Todo App</span>
</div>
</nav>
<div className="container pt-3 w-75">
<div className="d-flex">
<input type="text" className="form-control" value={todoText} onChange={handleChaneText} />
<button type="button" className="btn btn-primary ms-3" onClick={handleAddTodo}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-plus" viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
</svg>
</button>
</div>
<ShowTodoList />
</div>
</div>
)
}
export default Todo
解説
最初にimport文を書いて、先ほど作成したcss
とコンポーネントで使用するuseState
を読み込んでいます。
import './Todo.css'
import React, { useState } from 'react'
その後に関数コンポーネントを定義しています。
export const Todo = () => {
// コンポーネントの内容
}
export default Todo
最後にexportを書くことで、他のファイルからimportできるようにしています。
useStateで変数初期化
コンポーネントの内容を確認していきます。
最初にuseState
を使って、変数を定義しています。
useState
はReact
で変数の状態を管理(保持・更新)するために使用する関数です。
const [todoText, setTodoText] = useState('')
const [todos, setTodoList] = useState([])
const
で定義した左側は、値を保持するための変数で、右側が値を更新するための関数になります。
上記の例で行くと、todoText
が変数で、setTodoText
がtodoText
に値を設定するための関数です。
その下にはTODO一覧の状態を保持するためのtodos
配列と設定するためのsetTodoList
を定義しています。
handleAddTodoの定義
その次にhandleAddTodo
を作成しました。
これは、TODOの追加ボタンが押された時に動かす関数です。
const handleAddTodo = () => {
if (todoText === '') {
alert('TODOを入力してください')
return
}
setTodoList([...todos, todoText ])
setTodoText('')
}
todoText
に入力したTODOを反映させますが、入ってなかったらアラートを表示します。
入っていた場合は先ほど定義したTODO一覧配列に、入力されたtodoText
を下記のように反映させています。
setTodoList([...todos, todoText ])
その後にsetTodoText
でtodoText
に入っている内容(テキストボックスの内容)を空にしています。
handleChangeTextの定義
次にhandleChaneText
を作成しました。
これはテキストボックスに入力された値をtodoText
変数に反映させるために作成しました。
const handleChaneText = (e) => {
setTodoText(e.target.value)
}
React
のonChangeイベントに、これを設定すると引数のe.target.value
で入力した値が取得できます。
取得した値をsetTodoText
関数でtodoText
に反映させています。
ShowTodoListコンポーネント
その次にShowTodoList
コンポーネントを作成しています。
これは、TODO一覧の行のひとつになります。
const ShowTodoList = () => {
if (todos.length === 0) {
return ''
}
const handleDeleteTodo = (paramIndex) => {
const newTodos = todos.filter((data, index) => { return index !== paramIndex })
setTodoList([...newTodos])
}
const list = todos.map((data, index) => {
return (
<li key={index} className="list-group-item d-flex justify-content-between align-items-center">
{data}
<button className='btn btn-danger' onClick={() => handleDeleteTodo(index)}>
<!---省略--->
</button>
</li>
)
})
return (
<ul className="list-group mt-3">
{list}
</ul>
)
}
まず、一覧配列(todos
)にデータがなかったら空文字列を返しています。
その次にhandleDeleteTodo
関数を定義しています。
これは削除アイコンを押された時に動く関数です。JavaScriptのfilterを使用して値を取り出して、対象の物以外をTodo一覧配列に設定しています。
次にlist
変数に対して、行のタグを作成しています。
一覧配列(todos
)をmap
関数を使用してループさせて、タグを返します。
data
に保持されている一覧のTodoデータが入っていて、index
は配列のキーになります。
li
タグのリストにTODOを表示させていて、削除アイコンを右側に配置しています。
削除アイコンを押したら、先ほど定義したhandleDeleteTodo
を呼び出して、一覧配列(todos
)から削除するといった形です。
最後にul
タグで作成したli
タグを囲って、呼び出し元に返します。
Todoコンポーネントのタグ定義
最後にTodoコンポーネントで返すタグを定義しています。
一番上にナビバーを配置し、その下に入力エリアと、TODO一覧(ShowTodoList
)を配置するといった形です。
return (
<div>
<nav className="navbar navbar-dark bg-dark">
<div className="container-fluid">
<span className="navbar-brand">Todo App</span>
</div>
</nav>
<div className="container pt-3 w-75">
<div className="d-flex">
<input type="text" className="form-control" value={todoText} onChange={handleChaneText} />
<button type="button" className="btn btn-primary ms-3" onClick={handleAddTodo}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-plus" viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
</svg>
</button>
</div>
<ShowTodoList />
</div>
</div>
)
テキストボックスの内容を変えた場合にonChange
が動きます。
自作していたhandleChaneText
関数を設定しています。テキストの変更時に動いて、todoText
変数に入力した内容が更新されます。
追加ボタンを押すと、onClick
に設定しているhandleAddTodo
関数が動きます。
この関数で、一覧配列(todos
)にtodoText
変数の値を追加します。
追加された内容が、ShowTodoList
コンポーネントで表示されます。
index.jsで作成したコンポーネントを読み込む
作成したコンポーネントを最初から用意されているApp
コンポーネントからTodo
に切り替えます。
初期状態ではsrc/index.js
で、App
を読み込んでコンポーネントを表示しています。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// ---ここから削除---
// import App from './App';
// ---ここまで削除---
import Todo from './Todo'; //追加する
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
{/*---ここから削除---
<App />
---ここまで削除---*/}
<Todo /> {/*---追加する---*/}
</React.StrictMode>,
document.getElementById('root')
);
//---省略---
import文でTodoコンポーネントを呼び出した後に、App
コンポーネントからTodo
コンポーネントに切り替えました。
動作を確認する
プロジェクトを作成したディレクトリでnpm start
コマンドを実行すると、作成したアプリケーションが動きます。
`http://localhost:3000/` にアクセスして開くと、下記のようにTODOを入力するテキストボックスと追加ボタンが表示されます。
入力して、追加ボタンを押すとリストが増えます。
削除ボタンを押すと消すことが可能です。
終わりに
ソースコードは、下記githubリポジトリで公開しています。
コメント