【React】Reactの練習!ToDoリストを作ってみました。


今回はJavaScriptのフレームワークであるReactの練習として、ToDoリストを作ってみたので、コードや感想について記事にしていきます。
今回は、JavaScriptフレームワークであるReactを使ってToDoリストを作りました。
Todoリストについては、覚えるための重要な要素が詰まっているということで、さまざまな勉強教材で取り上げられているものと思います。
今回は、単純なToDoアプリだけど、コンポーネントを分割したり、TypeScriptやSCSSとの連携を学ぶために、色々な機能を使って作ってみたので、紹介していきます。
- Reactを使ったToDoリストの作り方
- コンポーネント分割した場合のデータの受け渡しの難しさ
- 自分で考えてプログラムを組むことで覚えられること
ReactでToDoリストを作った理由

まずは、なぜ今回Reactを使った理由について、説明していきます。
今回、Webアプリ開発をしたいと思い、以前少し勉強していたHTML、CSS、JavaScriptを勉強し直しました。
その中で、現在は生のJavaScriptではなく、フレームワークが使われていることを知り、色々と試してみようと思い、勉強をし始めました。
フレームワークで有名なのは、
- React(リアクト)
- Vue(ビュー)
- Angular(アンギュラー)
がありますが、世界ではReactがシェアを取り始めており、日本ではReactとVueの2つが強いらしいという情報を得て、まずはVue、その後にReactの基本文法を勉強してみました。
使用感的には、触りだけの感想ですが、文法などは全く異なりますが、どちらもできることは同じようなで、若干の使用感や文化の違いを感じる程度でした。
しかし、情報量的には、Reactの方が多いと感じる場面が多々あることから、今後の成長性が高そうなReactを今回選んで、みています。
そして、自分の頭で考えて何か作ってみようと思い、コンポーネントの分割を意識しながら、TypeScriptやSCSSを取り入れて、ToDoアプリをReactで作ることにしました。
作成環境について

今回は、大変便利な練習環境であるcodesandboxを使ったんだにゃ。
自分のPCに環境構築もできるのですが、あくまでも文法や考え方を中心に作成したかったことから、サーバ上で練習ができるcodesandboxというサイトを使用して、作成しています。
このサイトでは、JavaScriptに関するあらゆる環境を選んで使用することができ、その中には
「React + TypeScript」もあることから、その環境を選んで使用しています。
バージョンやプラグインも選ぶことができるので、加えてSCSSを追加しました。
完成したToDoリスト

まずは、完成したToDoリストについて紹介していきます。
作ったリストは下の画像の通りです。

- 「やるべきことを入力」のインプットボックスに入力し「add」でリストに追加
- 追加したリストは、「チェックボックス」にチェックを入れると消し線で文字が消える
- 「delete」を押すことでリストから削除

シンプルなToDoリストなんだにゃ。ただ、必要な要素はたっぷりと詰まっているんだにゃ。
コンポーネントの構成

それでは、コンポーネントの構成についてみていきます。
可能な限りコンポーネントを分割したかったので、色々と分割しています。

分割しているのは、ボタン、入力部分、データ、ToDoリストです。詳細は次のような感じですね。
- 「ボタン部品」で「PrimaryButton」「SecondaryButton」を作成し、色が異なるボタンを2つ作れるようにしている。
- 「入力部品」で「InputSpace」を作成し、「入力場所」でボタンと合わせる
- 「リストの部品」で「TextUnit」を作り、「ToDoリスト場所」で部品を繰り返して表示
- 「データを入れる場所」でデータを管理。

最終的に「App.tsx」で合わせて「index.tsx」を経由して表示しているんだにゃ。
苦労した点について

今回、迷ったり、苦労した点についても話しておきますね。
今回は、ToDoリストを作る上でいくつか大変な場面がありました。当然、これまで作ったこともあるので、基本的な考え方や作り方は知っていましたが、それでも迷いました。それらの点について説明していきます。
データの受け渡し
まずはデータの受け渡しです。
コンポーネントを分けていないときは、コンポーネント内や親から子、子から親程度のデータ受け渡ししかしませんでした。サンプルで練習する場合はここで止まっていました。
しかし、今回先述の通り、コンポーネントを分けたことで問題が生じました。
親子関係にもない場所からのデータ受け渡しが発生するからです。
例えば、入力欄の「add」ボタンを押すことで、別コンポーネントにある入力ボックスのデータをさらに別コンポーネントにあるリストに追加するという感じです。
図面にすると、次の感じですね。

ボタンを押すとリストに追加されます。
コードで考えると次のようになりますね。

PrimaryButtonを押すことで、別の場所にあるInputSpaceにあるテキストが、さらに別の場所にあるTextUnitに追加されることになります。
言葉で説明するとなんとなくできそうですが、意外とこの動作が難しいところでした。
今回は、この点についてproviderでデータを一括管理することにしました。
AddProviderにデータを渡して、そこにあるリストを表示していく感じです。
TypeScriptとSCSS
次は、TypeScriptとSCSSです。
ここに関しては完全な勉強不足ですが、いきなり使おうと思うと難しく感じました。
書き方や文法はわかるのですが、エラーだらけで、どこがどうなっているかがわかりませんでした。
特に型については、エラーだらけで大変でした。
もちろん利便性もありますし、事前にエラーが出るということは問題があるということになるので、いい点なのだと思います。
ただ、初めて使うと、これまでのJavaScriptがいかにガバガバだったんだなということに気が付かされました。
コンポーネントの構成
最後にコンポーネントの構成です。
今回は上記のような形としていますが、構成方法についてはかなり考えないといけないなと感じました。
単純なものであればそもそも分割する必要もない場面も多いでしょうが、これがどんどん複雑なアプリケーションだと、考える場面も多いのかと思います。
例えば、今回は色違いのボタンを2つだけにしましたが、場合によっては、5つや6つになる場合もあります。
そうなると、そもそもボタンのベースとなるボタンがあって、それを継承していく感じにした方がわかりやすくかつ作りやすいのではないかと思います。
このように、どこまで細分化させるか、抽象化させるかを考えないといけないという点は難しいなと思いました。
コード紹介

最後にコードを載せておきますので、参考としてください。
全体表示関係
index.html
最初に表示される「index.html」です。このhtmlの「root」idにreactがレンダリングされていきます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
index.tsx
「index.html」の「root」に繋がる大元の.tsxファイルです。ここが繋がりレンダリングされます。
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement!);
root.render(
<StrictMode>
<App />
</StrictMode>
);
App.tsx
分割したコンポーネントをまとめる「App.tsx」です。ここでは、「InputArea」「TextArea」の2つをまとめています。
SCSSについては、「margin」「padding」だけ0にしています。
「AddProvider」はデータ管理のためにインポートしています。
囲った場所で共通のデータを使うことができ、ここで囲うことで、囲われている「InputArea」「TextArea」内でデータを使うことができるようになります。
import styles from "./Main.module.scss";
import { InputArea } from "./components/InputArea";
import { TextArea } from "./components/TextArea";
import { AddProvider } from "./components/provider/AddProvider";
export default function App() {
return (
<AddProvider className={styles}>
<InputArea />
<TextArea />
</AddProvider>
);
}
Main.module.scss
*{
margin: 0;
padding: 0;
}
ボタン関係
PrimaryButton.tsx
「PrimaryButton.tsx」ではスタイルだけ青系統でまとめたボタンを作成しています。
ボタンの文字や押された際の効果は、プロップスで渡されるようにしています。
import { VFC } from "react";
import styles from "./Button.module.scss";
export const PrimaryButton: VFC<Props> = (props) => {
return (
<button
className={`${styles.btn} ${styles.primary}`}
onClick={props.onClickEvent}
>
{props.name}
</button>
);
};
SecondaryButton.tsx
「SecondaryButton.tsx」は赤系統でまとめていて、それ以外は「PrimaryButton.tsx」と同じです。
import { VFC } from "react";
import styles from "./Button.module.scss";
export const PrimaryButton: VFC<Props> = (props) => {
return (
<button
className={`${styles.btn} ${styles.primary}`}
onClick={props.onClickEvent}
>
{props.name}
</button>
);
};
Button.module.scss
SCSSらしく使う色を変数でまとめてみました。いちいち選ぶ必要がないので、楽ですね。
ホバーについても、&で繋ぐことができるので、コードがスッキリします。
$main-orange:#e4ab31;
$main-blue:#446296;
$main-gray:#cfdbe3;
$main-red: #9e2941;
.btn{
border-radius: 8px;
padding: 8px 16px;
margin: 5px;
border: 1px solid $main-gray;
font-size: 16px;
color: $main-gray;
&:hover{
background-color:$main-gray;
}
}
.primary{
background-color: $main-blue;
color: $main-gray;
&:hover{
color: $main-blue;
border: 1px solid $main-blue;
}
}
.secondary{
background-color: $main-red;
&:hover{
color: $main-red;
border: 1px solid $main-red;
}
}
インプットエリア
InputArea.tsx
import styles from "./Area.module.scss";
import { PrimaryButton } from "./button/PrimaryButton";
import { InputSpace } from "./input/InputSpace";
import { useContext } from "react";
import { AddContext } from "./provider/AddProvider";
type AddContextType = {
addInfo: Array<string>;
setAddInfo: Array<string>;
addText: string;
setAddText: string;
};
export const InputArea = () => {
const { addInfo, setAddInfo, addText, setAddText } = useContext<
AddContextType
>(AddContext);
const onClickEvent = () => {
if (addInfo.indexOf(addText) !== -1) {
alert("同じTODOは登録できません。");
setAddText("");
return;
}
if (addText === "") {
alert("TODOを入力してください。");
return;
}
const newAddInfo = [...addInfo, addText];
setAddInfo(newAddInfo);
setAddText("");
};
return (
<div className={styles.content}>
<InputSpace setAddText={setAddText} addText={addText} />
<PrimaryButton name="add" onClickEvent={onClickEvent} />
</div>
);
};
テキストエリア
TextArea.tsx
import { TextUnit } from "./TextUnit";
import { useContext } from "react";
import { AddContext } from "./provider/AddProvider";
export const TextArea = () => {
const { addInfo, setAddInfo } = useContext(AddContext);
return (
<div>
{addInfo.map((todo, index) => {
return (
<div key={todo}>
<TextUnit
text={todo}
index={index}
addInfo={addInfo}
setAddInfo={setAddInfo}
/>
</div>
);
})}
</div>
);
};
InputSpace.tsx
import { useState } from "react";
import styles from "./Input.module.scss";
export const InputSpace = (props: any) => {
const onChangeTodo = (e: any) => {
props.setAddText(e.target.value);
};
return (
<input
type="text"
className={styles.input}
placeholder="やるべきことの入力"
value={props.addText}
onChange={onChangeTodo}
/>
);
};
Input.module.scss
.input{
padding: 8px 16px;
border-radius: 8px;
outline: none;
margin: 5px;
}
データ関係
AddProvider.tsx
import { createContext, useState } from "react";
export const AddContext = createContext({});
export const AddProvider = (props: any) => {
const { children } = props;
const [addInfo, setAddInfo] = useState<string[]>([]);
const [addText, setAddText] = useState<string>("");
return (
<AddContext.Provider value={{ addInfo, setAddInfo, addText, setAddText }}>
{children}
</AddContext.Provider>
);
};
プログラミングを学ぶならTechAcademy(テックアカデミー)がおすすめ!

独学で苦戦している人は、一度しっかりと基礎を学ぶと次のステップへ進めますよ。今のおすすめは、TechAcademy(テックアカデミー)ですね。
- オンラインで選抜された現役エンジニアから学ぶことができる
- 自宅からWeb制作・プログラミング・アプリ開発など幅広い分野を学べる
- 学ぶだけではなく、転職の支援、副業に活かせるスキルの習得、副業の仕事紹介まで行っている

無料体験や相談もあるから、一度試してみてもいいんだにゃ!
\無料体験、無料相談はこちらから/