プログラミング

【React入門】ToDoリストを作って基礎知識を学ぼう【簡単】

React入門でToDoリストを作ってみよう.png

この記事では、これからReactに入門したいという方に向けた内容を書いていきます。

内容としては、Todoリストを実際にReactで作成していくというものになります。

この記事を通して、
・React開発の基礎
を身に付けられる内容となってます。

まず、基本的な文法を身に付けたら後は、ひたすら何かを作っていくという方法がプログラミング学習ではベストプラクティスだと思ってます。

ぜひ、Todoリストを作成してReactに入門してください。

本記事の内容
1なぜTodoリストなのか?
2環境構築
3JSXでモックを作成
4追加機能の実装
5削除機能の実装
6完了機能の実装
7戻す機能の実装
8コンポーネント化
9CSS in JS

0からReactエンジニアになるための手順は、全てここで解説しているので合わせてどうぞ。

Reactロードマップ
【完全無料ロードマップ】Reactエンジニアへなるための方法まとめ【現役Reactエンジニアが解説】Reactを学びたいと思ってますか?Reactエンジニアから勉強方法を学ぶのが一番効率的です。なので今回は、現役のReactエンジニアがReactの学習ロードマップを無料で公開します。Reactは今とても需要が高いスキルなので、今すぐ学びましょう。...

1なぜTodoリストなのか?

今回作成するアプリがなぜTodoリストなのか?

ということですが、Todoリストは基本的なシステムの主機能である、CRUDを有しているからです。

CRUDとは、

  • Create 作成
  • Read 編集
  • Update 更新
  • Delete 削除

です。

つまり、Todoリストが作れれば基礎はできるようになったということになります。

なので、今回はTodoリストという題材を選択しました。

2Reactの環境構築

まずは、環境構築をやりますが、いつも通りやるだけです。

nodeをインストールしたらcreate-react-appをしてアプリを作成します。(わからない人はググってください)

ここからはApp.jsのみを編集していきます。

まずは、一番外のdivタグ以外を全て削除してください。
後は、一番外のクラス名をapp-headerに変更すれば完了です。

ヘッダーではないので本来であれば不適切ですが、cssはいじりたくないですし、なんとなくこの背景色の方がカッコいいので 笑

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App-header">
    </div>
  );
}

export default App;

ローカルサーバーを立ち上げて、Reactの初期画面と同じ黒背景が表示されていれば環境構築は完了です。

また今回は最後まで、cssファイルは触らないのでそのままで大丈夫です。

3JSXでモックを作成

まずは、stateやpropsを持たない静的なモックを作っていきます。

最初にh1タグにTodoリストと入力します。

今回のアプリでは主に3つの領域があります。

  1. Todoの入力エリア
  2. 未完了のTodoエリア
  3. 完了のTodoエリア

です。

これらをdivタグで作っていきます。
まずはh2タイトルと、インプットエリア、追加ボタンを持った、Todoの入力エリアを作ります。

次に、h2タイトルと、未完了のTodoを持つulタグを作成します。
未完了のTodoには、完了ボタンと削除ボタンをそれぞれ持たせます。

最後に、h2のタイトルと、完了のTodoを持つulタグを作れば完成です。
完了のTodoには、戻すボタンを持たせます。

コンポーネントのソースは下記になります。

function App() {
  return (
    <div className="App-header">
      <h1>TODOリスト</h1>
      <div>
        <h2>入力エリア</h2>
        <input placeHolder="TODOを入力"/>
        <button>追加</button>
      </div>
      <div>
        <h2>未完了のTODO</h2>
        <ul>
          <li>TODO1</li>
          <button>完了</button>
          <button>削除</button>
          <li>TODO2</li>
          <button>完了</button>
          <button>削除</button>
        </ul>
      </div>
      <div>
        <h2>完了のTODO</h2>
          <ul>
            <li>TODO3</li>
            <button>戻す</button>
            <li>TODO4</li>
            <button>戻す</button>
          </ul>
        </div>
    </div>
  );
}

ちなみに、画面はこのようになっているかと。
default

次からはアプリに動的な機能を実装していきます。

4ToDoリストに追加機能を実装

インプットエリアに入力し、追加ボタンを押したものが未完了のTodoエリアに追加されていくような機能を実装していきます。

まずは、今アプリで使用するstateを一通り宣言していきます。

今回は、

  • 入力用にinputTodo
  • 未完了のTodo用にincompleteTodos
  • 完了のTodo用にcompleteTodos

という3つのstateを使います。

stateを用意したら、incompleteTodosとcompleteTodosという配列を、mapで回して表示するようにします。
このリストの要素にkeyを渡すのを忘れないようにしましょう。

次に、キーボードから入力したものがインプットエリアに表示されるようにしていきます。

これは、インプットエリアにonChangeイベントを持たせ、入力された値をsetInputTodoで変更させるようにすればOKです。

次に、追加ボタンが押された時の機能を実装していきます。

これはボタンが押された時に、inputTodoの値をincompleteTodosに追加するような機能を実装すれば完了です。

一応、空の状態でボタンが押された場合のことも考えて、空の場合はreturnするような実装にしておきましょう。
また、入力後はインプットエリアが空になるようにもしておきましょう。

現時点でのソースはこんな感じになります。

import './App.css';
import {useState} from "react";

function App() {
  const [inputTodo, setInputTodo] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState(["test1","test2"]);
  const [completeTodos, setCompleteTodos] = useState(["test3", "test4"]);

  const onChangeInputTodo = (e) => {
    setInputTodo(e.target.value);
  }

  const onClickAdd = () => {
    if(inputTodo === "") return;
    const newTodos = [...incompleteTodos, inputTodo];
    setIncompleteTodos(newTodos);
    setInputTodo("");
  }

  return (
    <div className="App-header">
      <h1>TODOリスト</h1>
      <div>
        <h2>入力エリア</h2>
        <input placeHolder="TODOを入力" value={inputTodo} onChange={onChangeInputTodo}/>
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div>
        <h2>未完了のTODO</h2>
        <ul>
          {incompleteTodos.map((todo) => {
          return(
            <div key={todo}>
              <li>{todo}</li>
              <button>完了</button>
              <button>削除</button>
            </div>
          )
          })}
        </ul>
      </div>
      <div>
        <h2>完了のTODO</h2>
          <ul>
            {completeTodos.map((todo) => {
            return(
              <div key={todo}>
                <li>{todo}</li>
                <button>戻す</button>
              </div>
            )
            })}
          </ul>
        </div>
    </div>
  );
}

export default App;

これで、追加ボタンを押すと要素が追加されるようになっているかと思います。

5削除機能の実装

削除ボタンが押された時に、そのTodoが削除されるような機能を実装していきます。

これは、ボタンが押された時に押されたTodoを削除するようなクリックイベントを付与すれば完成します。

しかし、現時点では押されたTodoがどれなのかを判別する手段がありません。
なので、mapにindexを追加しましょう。

そして、クリックイベントにindexを引数として渡すようにしてやれば、配列の何番目の要素が押されたかを判別できます。

後は、そのindexから1つを削除するためにspliceを使い、新しく作成した配列をsetIncompleteTodosで設定してやれば完了です。

ソースは下記になります。

import './App.css';
import {useState} from "react";

function App() {
  const [inputTodo, setInputTodo] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState(["test1","test2"]);
  const [completeTodos, setCompleteTodos] = useState(["test3", "test4"]);

  const onChangeInputTodo = (e) => {
    setInputTodo(e.target.value);
  }

  const onClickAdd = () => {
    if(inputTodo === "") return;
    const newTodos = [...incompleteTodos, inputTodo];
    setIncompleteTodos(newTodos);
    setInputTodo("");
  }

  const onClickDelete = (i) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(i, 1);
    setIncompleteTodos(newTodos);
  }

  return (
    <div className="App-header">
      <h1>TODOリスト</h1>
      <div>
        <h2>入力エリア</h2>
        <input placeHolder="TODOを入力" value={inputTodo} onChange={onChangeInputTodo}/>
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div>
        <h2>未完了のTODO</h2>
        <ul>
          {incompleteTodos.map((todo, index) => {
          return(
            <div key={todo}>
              <li>{todo}</li>
              <button>完了</button>
              <button onClick={() => onClickDelete(index)}>削除</button>
            </div>
          )
          })}
        </ul>
      </div>
      <div>
        <h2>完了のTODO</h2>
          <ul>
            {completeTodos.map((todo) => {
            return(
              <div key={todo}>
                <li>{todo}</li>
                <button>戻す</button>
              </div>
            )
            })}
          </ul>
        </div>
    </div>
  );
}

export default App;

ちなみに、わざわざnewTodosという配列のコピーを作っているのはstateはset~以外で編集ができないからです。

6完了機能の実装

次は完了ボタンが押された時に、そのTodoを完了のTodoエリアに追加するような機能になります。

この関数にも引数としてindexを渡します。

そして、incompleteTodosからそのindex番目の要素を削除します。
後は、削除した要素をcompleteTodosに追加すればOKです。

コードで表現すると以下のようになります。

 const onClickComplete = (i) => {
    const newIncompleteTodos = [...incompleteTodos];
    const newCompleteTodos = [...completeTodos, incompleteTodos[i]];

    newIncompleteTodos.splice(i, 1);

    setCompleteTodos(newCompleteTodos)
    setIncompleteTodos(newIncompleteTodos);
  }

この関数を完了ボタンに持たせてあげれば完成です。

7戻す機能の実装

最後の機能として、押された時に完了のTodoから未完了のTodoを移動させる、戻すボタンを実装します。

これも完了ボタンとやることは一緒です。

まず、indexを引数で渡してやり、受け取ったindex番目の要素をcompleteTodosから削除します。

そして、その削除した要素をincompleteTodosに追加してやれば終わりです。

const onClickReturn = (i) => {
    const newIncompleteTodos = [...incompleteTodos, completeTodos[i]];
    const newCompleteTodos = [...completeTodos];

    newCompleteTodos.splice(i, 1);

    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  }

後は、デフォルトで持たせていたincompleteTodosとcompleteTodosの要素を空にすればアプリ自体も完成です。
完成時のコードはこちらになります。

import './App.css';
import {useState} from "react";

function App() {
  const [inputTodo, setInputTodo] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState([]);
  const [completeTodos, setCompleteTodos] = useState([]);

  const onChangeInputTodo = (e) => {
    setInputTodo(e.target.value);
  }

  const onClickAdd = () => {
    if(inputTodo === "") return;
    const newTodos = [...incompleteTodos, inputTodo];
    setIncompleteTodos(newTodos);
    setInputTodo("");
  }

  const onClickDelete = (i) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(i, 1);
    setIncompleteTodos(newTodos);
  }

  const onClickComplete = (i) => {
    const newIncompleteTodos = [...incompleteTodos];
    const newCompleteTodos = [...completeTodos, incompleteTodos[i]];

    newIncompleteTodos.splice(i, 1);

    setCompleteTodos(newCompleteTodos)
    setIncompleteTodos(newIncompleteTodos);
  }

  const onClickReturn = (i) => {
    const newIncompleteTodos = [...incompleteTodos, completeTodos[i]];
    const newCompleteTodos = [...completeTodos];

    newCompleteTodos.splice(i, 1);

    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  }

  return (
    <div className="App-header">
      <h1>TODOリスト</h1>
      <div>
        <h2>入力エリア</h2>
        <input placeHolder="TODOを入力" value={inputTodo} onChange={onChangeInputTodo}/>
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div>
        <h2>未完了のTODO</h2>
        <ul>
          {incompleteTodos.map((todo, index) => {
          return(
            <div key={todo}>
              <li>{todo}</li>
              <button onClick={() => onClickComplete(index)}>完了</button>
              <button onClick={() => onClickDelete(index)}>削除</button>
            </div>
          )
          })}
        </ul>
      </div>
      <div>
        <h2>完了のTODO</h2>
          <ul>
            {completeTodos.map((todo, index) => {
            return(
              <div key={todo}>
                <li>{todo}</li>
                <button onClick={() => onClickReturn(index)}>戻す</button>
              </div>
            )
            })}
          </ul>
        </div>
    </div>
  );
}

export default App;

8コンポーネント化

アプリ自体は完成したのですが、全て1ファイルにコードを書いているため、見通したが悪いですし、Reactっぽく無いです。

なので、いくつかの要素をコンポーネント化していきましょう。

コンポーネント化することで、コードが見通しがよくなりまし、コンポーネントの再利用などが可能になります。
今回は再利用する要素はありませんが。

さて、コンポーネント化をしていくのですが、分け方は最初にdivタグで作った

  • インプットエリア
  • 未完了のTodoエリア
  • 完了のTodoエリア

をそれぞれコンポーネント化していきます。

まずは、srcフォルダ配下にcomponentsフォルダを作ります。

インプットエリアのコンポーネント化

最初に、インプットエリアをコンポーネント化していくので、InputTodo.jsxというファイルを作成します。

そして、対象のdivタグを持ってきてexportしてやるだけです。

後は、ここで使用している、inputTodo、onChangeInputTodo、onClickAddをpropsで渡してやります。

コードはこちら

export const InputTodo = (props) => {
  const {inputTodo, onChangeInputTodo, onClickAdd} = props;
  return(
    <div>
        <h2>入力エリア</h2>
        <input placeHolder="TODOを入力" value={inputTodo} onChange={onChangeInputTodo}/>
        <button onClick={onClickAdd}>追加</button>
      </div>
  );
}

未完了のTodoエリアのコンポーネント化

次に未完了のTodoエリアです。

同じようにcomponents配下にIncompleteTodo.jsxを作成して、対象のdivタグをコピーします。

また、使っているstateや関数をpropsとして渡してやります。

export const IncompleteTodos = (props) => {
  const {incompleteTodos, onClickComplete, onClickDelete} = props;
  return(
    <div>
        <h2>未完了のTODO</h2>
        <ul>
          {incompleteTodos.map((todo, index) => {
          return(
            <div key={todo}>
              <li>{todo}</li>
              <button onClick={() => onClickComplete(index)}>完了</button>
              <button onClick={() => onClickDelete(index)}>削除</button>
            </div>
          )
          })}
        </ul>
      </div>
  );
}

完了のTodoエリアのコンポーネント化

同じようにCompleteTodos.jsxを作成すればOkです。

export const CompleteTodos = (props) => {
  const {completeTodos, onClickReturn} = props;
  return(
    <div style={{"backgroundColor" : "blue", "marginTop" : "18px", "marginBottom" : "50px" ,"padding" : "0px 18px"}}>
        <h2>完了のTODO</h2>
          <ul>
            {completeTodos.map((todo, index) => {
            return(
              <div key={todo}>
                <li>{todo}</li>
                <button onClick={() => onClickReturn(index)}>戻す</button>
              </div>
            )
            })}
          </ul>
        </div>
  );
}

後は、App.jsの方でこれらをimportすれば完成です。

import './App.css';
import {useState} from "react";
import {InputTodo} from "./components/InputTodo";
import { IncompleteTodos } from './components/IncompleteTodos';
import { CompleteTodos } from './components/CompleteTodos';

function App() {
  const [inputTodo, setInputTodo] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState([]);
  const [completeTodos, setCompleteTodos] = useState([]);

  const onChangeInputTodo = (e) => {
    setInputTodo(e.target.value);
  }

  const onClickAdd = () => {
    if(inputTodo === "") return;
    const newTodos = [...incompleteTodos, inputTodo];
    setIncompleteTodos(newTodos);
    setInputTodo("");
  }

  const onClickDelete = (i) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(i, 1);
    setIncompleteTodos(newTodos);
  }

  const onClickComplete = (i) => {
    const newIncompleteTodos = [...incompleteTodos];
    const newCompleteTodos = [...completeTodos, incompleteTodos[i]];

    newIncompleteTodos.splice(i, 1);

    setCompleteTodos(newCompleteTodos)
    setIncompleteTodos(newIncompleteTodos);
  }

  const onClickReturn = (i) => {
    const newIncompleteTodos = [...incompleteTodos, completeTodos[i]];
    const newCompleteTodos = [...completeTodos];

    newCompleteTodos.splice(i, 1);

    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  }

  return (
    <div className="App-header">
      <h1>TODOリスト</h1>
      <InputTodo inputTodo={inputTodo} onChangeInputTodo={onChangeInputTodo} onClickAdd={onClickAdd}/>
      <IncompleteTodos incompleteTodos={incompleteTodos} onClickComplete={onClickComplete} onClickDelete={onClickDelete} />
      <CompleteTodos completeTodos={completeTodos} onClickReturn={onClickReturn}/>
    </div>
  );
}

export default App;

おさらいしておきますと、ファイル構成は以下のようになります。

src
-components
–InputTodo.jsx
–IncompleteTodo.jsx
–CompleteTodo.jsx
-App.js

9CSS in JS

最後におまけの勉強として、jsxファイル内でcssを指定する方法を紹介します。

まずは、cssを設定したいタグを決め、そこにstyle属性を追加します。

そこに、jsのオブジェクトしてcssのセレクタとプロパティを設定するだです。

実際に見ていきましょう。

export const InputTodo = (props) => {
  const {inputTodo, onChangeInputTodo, onClickAdd} = props;
  return(
    <div style={{"border": "1px solid white", "padding" : "9px 36px"}}>
export const IncompleteTodos = (props) => {
  const {incompleteTodos, onClickComplete, onClickDelete} = props;
  return(
    <div style={{"backgroundColor" : "lightblue", "marginTop" : "18px", "padding" : "0px 18px"}}>
export const CompleteTodos = (props) => {
  const {completeTodos, onClickReturn} = props;
  return(
    <div style={{"backgroundColor" : "blue", "marginTop" : "18px", "marginBottom" : "50px" ,"padding" : "0px 18px"}}>

正直ダサイですが、cssの書き方さえ学べれば良いと思うので、お許しください。。

css指定後の画面はこのようになっているかと思います。
todo-css

【React入門】ToDoリストを作って基礎知識を学ぼう まとめ

今記事では、Reactを使ってTodoリストを作ってきました。

コードは全てこちらにもありますので、参考にどうぞ。
https://github.com/hinoharashinya/react-todolist

このように、何かを学んだ後は実際に何か作ってみるという勉強法が最強だと思いますので、色々作ってどんどん知識を深めていきましょう。

また、リファクタリングしたり、追加機能を実装したりするのも良い勉強になると思うので、やってみてください。

僕のブログでは他にもReactのチュートリアルやプログラミングについての記事を書いてますので、ぜひ参考にしてみてください。

Reactエンジニアになりたい人はこちらからどうぞ

Reactロードマップ
【完全無料ロードマップ】Reactエンジニアへなるための方法まとめ【現役Reactエンジニアが解説】Reactを学びたいと思ってますか?Reactエンジニアから勉強方法を学ぶのが一番効率的です。なので今回は、現役のReactエンジニアがReactの学習ロードマップを無料で公開します。Reactは今とても需要が高いスキルなので、今すぐ学びましょう。...

おわり

ABOUT ME
ひのしん
【執筆者情報】ひのしんです。「正しい努力をすれば誰でも成果は出せる」 をモットーに発信しています。元ニートの現フリーランスエンジニアです。 プログラミング歴は4年ほどで、TOEIC885点を取得。科学と実体験を元にした効率的で正しいスキルアップ(プログラミング・英語・ブログ)の方法を発信してます。
\\こちらの記事が大好評です//