LoginSignup
1
4

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-01-01

初めに

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

私の環境

❯ 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アプリ作成方法

以下のようなコードで、簡単にtodoアプリが出来ます
スクリーンショット 2023-01-01 21.45.00.png

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

削除ボタンも追加する場合は、以下のように書きます

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

ディレクトリ構成

スクリーンショット 2023-01-02 11.49.47.png

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>
  )
}
todos-app/styles/Index.module.css
.done{
  text-decoration: line-through;
}

参考文献

めちゃくちゃオススメです!
React & TypeScriptを学ぶのに最適です!
初心者に本当におすすめ
じゃけぇ本

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

udemyで超おすすめのjs講座
udemyで超おすすめのreact講座
js自体をちゃんと学ぶのも大事だと思うので、この本をオススメします

今後

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

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4