初めに
React & TypeScriptを通しての、todoアプリの作り方を紹介します。
とても基礎的なアプリなので、実際にコードを読んで試してみて、参考にしてください!
対象読者:reactを勉強し始めて浅い方(勉強した事がある人が対象です!)
初めて、reactで簡単なアプリを作ってみよう!と考えた方におすすめです
向かない方:reactを勉強して長い方 & 「react&JavaScript」を勉強した事がない方
環境&構築
node.js等が入っていない人は、こちらの記事を参考にお願いします。
https://clover-weblog.com/nextjs-start/
手順
環境が構築が済んだら、以下のコマンドで作成
npx create-next-app todos-app --typescript
package.jsonは以下のようなバージョンで作成されました(2023/01/02時点)
私の環境
❯ node -v
v16.14.2
% npx -v
8.19.2
"react": "^18.2.0"
- react18系
- node.js 16.14.2
-
npx create-next-app todos-app --typescript
で作成
補足
この記事に興味を持ちましたら、以下のハンズオンをやることをオススメします。
reactでtodoを学ぶなら本当におすすめのzennの記事
解説も素晴らしく、本当に良い記事だと思います
基本のtodoアプリ作成方法
import { useState } from 'react';
export default function index() {
const [text, setText] = useState<string>("");
const [todos, setTodos] = useState<string[]>([]);
// 送信されてきた値をstateに保存
function onChange(e:React.ChangeEvent<HTMLInputElement>) {
setText(e.target.value);
}
// 送信ボタンを押した後、入力フォームに書かれている値をtodosのstateに保存
function onSubmit(e:React.FormEvent<HTMLFormElement>) {
// 送信ボタンを押した後の画面遷移を防ぐ
e.preventDefault();
// 新しくtodosのstateを更新するための、仮の配列
const newTodos = [...todos];
// 入力フォームに書かれている値を追加する
newTodos.push(text);
setTodos(newTodos);
setText("");
}
return (
<div>
<h1>todos</h1>
{/* リロードを防ぐために、イベントオブジェクトを渡す */}
<form onSubmit={(e) => {onSubmit(e)}}>
<input type="text" value={text} onChange={onChange} />
<button type='submit' onSubmit={() => onSubmit}>送信</button>
</form>
<div>
<ul>
{/* 登録されているtodosを展開する */}
{todos.map((todo,index) => {
// mapで展開する値には、keyプロパティを指定して、ユニークな値を渡す(reactが、一つ一つのエレメントを識別するために)
return <li key={index}>{todo}</li>
})}
</ul>
</div>
</div>
)
}
削除ボタンも追加する
削除ボタンも追加する場合は、以下のように書きます
import { useState } from 'react';
// todoの型定義。mapで特定するためにidも付与
type Todos = {
id:number,
text:string
}
export default function index() {
const [text, setText] = useState<string>("");
// todoは複数あるので、配列で作成
const [todos, setTodos] = useState<Todos[]>([]);
// 送信されてきた値をstateに保存
function onChange(e:React.ChangeEvent<HTMLInputElement>) {
setText(e.target.value);
}
function onSubmit(e:React.FormEvent<HTMLFormElement>) {
// 送信ボタンを押した後の画面遷移を防ぐ
e.preventDefault();
// todosに保存するための、新しいtodoを作成
const newTodo: Todos = {
id: new Date().getTime(), // ユニークな値なら何でも良い
text: text // stateのtext(入力フォームに書かれている値)を保存
}
// todosを新しく作り直すので、既存のtodosに、newTodoを足す
const newTodos = [...todos,newTodo];
setTodos(newTodos); // 更新
setText("");
}
function onDelete(id:number) {
// 削除するボタンを押した後、指定されたtodoを消すのだが、その前に、完全に別物のtodosのコピーを作成する必要がある
// オブジェクト型をコピーすると、通常は参照先をコピーしてしまうので、そうならないようにするmapの戻り値で新しい配列を作成する。
// こちらの、「shallow copyとdeep copy」の記事を読むと、オブジェクト型がどのようなコピーをするのかが分かると思いますhttps://www.wakuwakubank.com/posts/744-javascript-object-copy/
const deepCopy = todos.map((todo) => ({ ...todo }));
deepCopy.map((todo,index) => {
if (todo.id === id) {
// 既存のtodoのidと、引数で渡ってきたidが同じだったら、spliceメソッドで削除する
deepCopy.splice(index,1)
}
});
// 先程のdeepCopyを渡す事で、stateが更新されて削除される
setTodos(deepCopy);
}
return (
<div>
<h1>todos</h1>
{/* リロードを防ぐために、イベントオブジェクトを渡す */}
<form onSubmit={(e) => {onSubmit(e)}}>
{/* 入力された値をstateに保存する */}
<input type="text" value={text} onChange={onChange} />
{/* 送信ボタンを押したら、todosに新しいtodoを追加する */}
<button type='submit' onClick={() => onSubmit}>送信</button>
</form>
<div>
<ul>
{/* 登録されているtodosを展開する */}
{todos.map((todo) => {
return <li key={todo.id}>
{todo.text}
{/* 削除ボタンを押した時に、todoのidを渡す事で特定出来るようにし、spliceメソッドで削除する */}
<button type="submit" onClick={() => onDelete(todo.id)}>削除</button>
</li>
})}
</ul>
</div>
</div>
)
}
完了した場合、チェックを付けると、打ち消し線が付くようにする
ディレクトリ構成
import { useState } from 'react';
// チェックボックス入力後、cssで打ち消し線を実装したいため使用
import classes from "../styles/Index.module.css"
// todoの型定義。mapで特定するためにidも付与
type Todos = {
id:number,
text:string,
isDone:boolean
}
export default function index() {
const [text, setText] = useState<string>("");
// todoは複数あるので、配列で作成
const [todos, setTodos] = useState<Todos[]>([]);
// 送信されてきた値をstateに保存
function onChange(e:React.ChangeEvent<HTMLInputElement>) {
setText(e.target.value);
}
function onSubmit(e:React.FormEvent<HTMLFormElement>) {
// 送信ボタンを押した後の画面遷移を防ぐ
e.preventDefault();
// todosに保存するための、新しいtodoを作成
const newTodo: Todos = {
id: new Date().getTime(), // ユニークな値なら何でも良い
text: text, // stateのtext(入力フォームに書かれている値)を保存
isDone:false
}
// todosを新しく作り直すので、既存のtodosに、newTodoを足す
const newTodos = [...todos,newTodo];
setTodos(newTodos); // 更新
setText("");
}
function onDelete(id:number) {
// 削除するボタンを押した後、指定されたtodoを消すのだが、その前に、完全に別物のtodosのコピーを作成する必要がある
// オブジェクト型をコピーすると、通常は参照先をコピーしてしまうので、そうならないようにするmapの戻り値で新しい配列を作成する。
// こちらの、「shallow copyとdeep copy」の記事を読むと、オブジェクト型がどのようなコピーをするのかが分かると思いますhttps://www.wakuwakubank.com/posts/744-javascript-object-copy/
const deepCopy = todos.map((todo) => ({ ...todo }));
deepCopy.map((todo,index) => {
if (todo.id === id) {
// 既存のtodoのidと、引数で渡ってきたidが同じだったら、spliceメソッドで削除する
deepCopy.splice(index,1)
}
});
// 先程のdeepCopyを渡す事で、stateが更新されて削除される
setTodos(deepCopy);
}
function onCheck(id:number) {
const deepCopy = todos.map((todo) => ({ ...todo }));
const newTodos = deepCopy.map((todo) => {
if (todo.id === id) {
// クリックする度に、booleanの値を反転する(!で)
todo.isDone = !todo.isDone;
}
return todo
});
// boolean反転させた結果で、再作成
setTodos(newTodos);
}
return (
<div>
<h1>todos</h1>
{/* リロードを防ぐために、イベントオブジェクトを渡す */}
<form onSubmit={(e) => {onSubmit(e)}}>
{/* 入力された値をstateに保存する */}
<input type="text" value={text} onChange={onChange} />
{/* 送信ボタンを押したら、todosに新しいtodoを追加する */}
<button type='submit' onClick={() => onSubmit}>送信</button>
</form>
<div>
<ul>
{/* 登録されているtodosを展開する */}
{todos.map((todo) => {
return <li key={todo.id} className={todo.isDone ? classes.done : ""}>
<input type="checkbox" onClick={() => onCheck(todo.id)} />
{todo.text}
{/* 削除ボタンを押した時に、todoのidを渡す事で特定出来るようにし、spliceメソッドで削除する */}
<button type="submit" onClick={() => onDelete(todo.id)}>削除</button>
</li>
})}
</ul>
</div>
</div>
)
}
.done{
text-decoration: line-through;
}
参考文献
めちゃくちゃオススメです!
React & TypeScriptを学ぶのに最適です!
初心者に本当におすすめ
じゃけぇ本
めちゃくちゃわかりやすいtodoアプリの作り方。
参考にさせていただきました(1000円払わなくても、途中までは出来ます)
reactでtodoを学ぶなら本当におすすめのzennの記事
udemyで超おすすめのjs講座
udemyで超おすすめのreact講座
js自体をちゃんと学ぶのも大事だと思うので、この本をオススメします
今後
色々消化不良なので、もう少し色々修正して記事を出します