超初心者向け！ React & TypeScriptで簡易todoアプリ作成方法

# 初めに

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時点)
![スクリーンショット 2023-01-02 11.54.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492674/cc3ab557-fd24-199d-191b-9ca07a062ecc.png)

### 私の環境
```
❯ 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の記事](https://zenn.dev/sprout2000/books/76a279bb90c3f3)
解説も素晴らしく、本当に良い記事だと思います

# 基本のtodoアプリ作成方法
以下のようなコードで、簡単にtodoアプリが出来ます
![スクリーンショット 2023-01-01 21.45.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492674/49ab4d7a-c447-7b8d-6a50-761e8067103a.png)

```tsx:index.tsx
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>
  )
}
```

# 削除ボタンも追加する
![スクリーンショット 2023-01-01 21.46.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492674/2c4b712f-10bb-966d-c9a2-6bc0ec242e3d.png)


削除ボタンも追加する場合は、以下のように書きます
```tsx:index.tsx  
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>
  )
}
```

# 完了した場合、チェックを付けると、打ち消し線が付くようにする
![スクリーンショット 2023-01-02 11.44.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492674/23c15289-f2a9-1f34-fd78-e6c150e62a18.png)

### ディレクトリ構成
![スクリーンショット 2023-01-02 11.49.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492674/85e7e116-63ae-4614-353a-d49fa2c89dce.png)

```tsx:pages/index.tsx
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>
  )
}
```

```css:todos-app/styles/Index.module.css
.done{
  text-decoration: line-through;
}
```

# 参考文献

めちゃくちゃオススメです！
React & TypeScriptを学ぶのに最適です！
初心者に本当におすすめ
[じゃけぇ本](https://www.amazon.co.jp/%E3%83%A2%E3%83%80%E3%83%B3JavaScript%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%8B%E3%82%89%E5%A7%8B%E3%82%81%E3%82%8B-React%E5%AE%9F%E8%B7%B5%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-%EF%BC%88%E6%9C%80%E6%96%B0ReactHooks%E5%AF%BE%E5%BF%9C%EF%BC%89-%E3%81%98%E3%82%83%E3%81%91%E3%81%87%EF%BC%88%E5%B2%A1%E7%94%B0-%E6%8B%93%E5%B7%B3%EF%BC%89-ebook/dp/B09BV2HGN3)

めちゃくちゃわかりやすいtodoアプリの作り方。
参考にさせていただきました(1000円払わなくても、途中までは出来ます)
[reactでtodoを学ぶなら本当におすすめのzennの記事](https://zenn.dev/sprout2000/books/76a279bb90c3f3)

[udemyで超おすすめのjs講座](https://www.udemy.com/course/javascript-essence/)
[udemyで超おすすめのreact講座](https://www.udemy.com/course/react-complete-guide/)
[js自体をちゃんと学ぶのも大事だと思うので、この本をオススメします](https://www.amazon.co.jp/%E7%8B%AC%E7%BF%92JavaScript-%E6%96%B0%E7%89%88-CodeMafia-%E5%A4%96%E6%9D%91-%E5%B0%86%E5%A4%A7/dp/479816027X)

# 今後

色々消化不良なので、もう少し色々修正して記事を出します