はじめに
今回のハンズオンではReactをこれからやっていきたい人に向けてTODOアプリを作りながらReactの基本的な使い方から実際にアプリケーションをデプロイしてユーザーが使えるリリースまでを行えます。
アプリケーションはとてもシンプルなものですので、短時間で学習することが可能です
初心者向けに本記事は書いておりますので、丁寧にセットアップから紹介していきます。
JavaScriptなどの前提知識は不要で、HTMLとCSSの知識があれば行っていただけます
またこのハンズオンでは最近人気がでているShadcn/uiを活用しており、学習した後に簡単にカスタマイズをしていただけるはずです。
ハンズオンだけでは使える実力はつきません。ぜひともここで習ったことを活かしながら、似たようなアプリケーションを作成してみてください
動画で学びたい方へ
こちらのハンズオンはYoutubeの動画でも公開しております
より詳しく説明を聞きたい方はぜひご覧ください
本ハンズオンで学べること
- JavaScriptの基本的な構文
- Reactの使い方 (useState, イベント)
- Shadcn/uiの使い方
- TailwindCSSの使い方
- Firebaseのデプロイ方法
- react-iconsの利用方法
対象者
- HTML/CSSの経験がある
- Reactに興味があるが触ったことがない
- 手を動かして学びたい
- デプロイをしたことがない
1. 環境構築
まずはReact/TypeScript/Shadcn(TawilwindCSS)の環境から作ります。
PCのターミナルを開いてまずはnode
とnpm
をインストールします
Windowsの方
https://prog-8.com/docs/nodejs-env-win
Macの方
$ brew install node
Ubuntuの方
$ sudo apt install -y nodejs npm
インストールできたら確認をします
$ npm -v
Node.js v18.17.0
$ node -v
次にViteを利用してReact環境を作ります
$ npm create vite@latest
✔ Project name: … first-todo
✔ Select a framework: › React
✔ Select a variant: › TypeScript
これでReactの環境ができました。
実際に起動をしていきます
$ cd first-todo
$ npm i
$ npm run dev
localhost:5173にアクセスすると以下の画面が表示されます
次にShadcnを導入します
別のターミナルを開いてfirst-todo
ディレクトリに移動してインストールします
$ npm install -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p
$ npm i -D @types/node
first-todoディレクトリをVSCodeで開いてください
tsconfig.json
を変更します
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
vite.config.ts
を変更します
import path from "path"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})
shadcn/uiをインストールします
$ npx shadcn-ui@latest init
✔ Would you like to use TypeScript (recommended)? … no / yes
✔ Which style would you like to use? › Default
✔ Which color would you like to use as base color? › Slate
✔ Where is your global CSS file? … src/index.css
✔ Would you like to use CSS variables for colors? … no / yes
✔ Are you using a custom tailwind prefix eg. tw-? (Leave blank if not) …
✔ Where is your tailwind.config.js located? … tailwind.config.js
✔ Configure the import alias for components: … @/components
✔ Configure the import alias for utils: … @/lib/utils
✔ Are you using React Server Components? … no / yes
✔ Write configuration to components.json. Proceed? … yes
ポイントはReacct Server ComponentをNoにすることです
これで導入が完了したので、ボタンを表示してみます
まずはボタンをインストールします
$ npx shadcn-ui@latest add button
src/App.tsx
を以下にします
import { Button } from "@/components/ui/button"
export default function Home() {
return (
<div>
<Button>Click me</Button>
</div>
)
}
localhost:5173をみると黒いボタンが表示されます
shadcnの導入ができました!
2. TODOアプリのデザインを作る
まずはTODOアプリのデザイン部分を作成していきます
機能面はこのあと作成していきます
shadcnのCardとInputを利用します
https://ui.shadcn.com/docs/components/card
https://ui.shadcn.com/docs/components/input
$ npx shadcn-ui@latest add card
$ npx shadcn-ui@latest add input
import { Button } from "./components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "./components/ui/card";
import { Input } from "./components/ui/input";
export default function Home() {
const todos = ["掃除する", "洗濯する", "料理する"];
return (
<div className="bg-gray-100 flex justify-center items-center min-h-screen">
<Card className="w-[400px]">
<CardHeader>
<CardTitle>TODO App</CardTitle>
</CardHeader>
<CardContent>
<Input placeholder="タスクを追加" />
<Button className="w-full mt-2">追加</Button>
<ul>
{todos.map((todo) => (
<li className="bg-white p-2 mt-2 flex">
<div>・{todo}</div>
<button className="ml-2">削除する</button>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
);
}
ReactではHTMLの部分とjsの部分を1つのファイルに書くことができます
<ul>
{todos.map((todo) => (
<li className="bg-white p-2 mt-2 flex">
<div>・{todo}</div>
<button className="ml-2">削除する</button>
</li>
))}
</ul>
このようにHTMLのなかでmapを回して
を作ることでTODOを表示していますスタイリングにはTailwindCSSを使っています
<div className="bg-gray-100 flex justify-center items-center min-h-screen">
このようにcssをクラスで当てることができます
- bg-gray-100 : 背景色
- flex : display : フレックスにする
- justiyf-center : 要素は右左で中心に配置
- items-center : 要素を上下で中心に配置
このようにCSSを細かく当てなくてもできるのがTailwindCSSです
<Card>
など大文字で始まるタグはShadcnのコンポーネントで、タグを利用することで簡単におしゃれなデザインが利用可能です
3. TODOを追加できるようにする
ここからはReactの内容になっていきます
まずはTODOを追加できるようにしていきましょう
import { useState } from "react"; //追加
import { Button } from "./components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "./components/ui/card";
import { Input } from "./components/ui/input";
export default function Home() {
const [todo, setTodo] = useState(""); //追加
const todos = ["掃除する", "洗濯する", "料理する"];
return (
<div className="bg-gray-100 flex justify-center items-center min-h-screen">
<Card className="w-[400px]">
<CardHeader>
<CardTitle>TODO App</CardTitle>
</CardHeader>
<CardContent>
<Input
placeholder="タスクを追加"
onChange={(e) => setTodo(e.target.value)} //追加
value={todo} //ついか
/>
<div>{todo}</div> //追加
<Button className="w-full mt-2">追加</Button>
<ul>
{todos.map((todo) => (
<li className="bg-white p-2 mt-2 flex">
<div>・{todo}</div>
<button className="ml-2">削除する</button>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
);
}
ここまで実装すると入力した内容がフォームの下に表示されるようになります
画面の状態を変更したい場合(ここではtodoの内容)はuseState
を使います
const [todo, setTodo] = useState("");
todoは現在の値
setTodoはtodoを変更するための関数
初期値は""(空)になっています
フォームに注目します
<Input
placeholder="タスクを追加"
onChange={(e) => setTodo(e.target.value)} //追加
/>
フォームではonChange
というのを追加しました
これはフォームの内容が変わったら実行する関数を書くことができます
setTodo
を使うことで入力した内容を反映しています
なので入力をするとtodo
の内容が変わります
<div>{todo}</div>
では、TODOを追加できるように変更していきましょう
import { useState } from "react";
import { Button } from "./components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "./components/ui/card";
import { Input } from "./components/ui/input";
export default function Home() {
const [todo, setTodo] = useState("");
const [todos, setTodos] = useState<string[]>([]); // 追加
// const todos = ["掃除する", "洗濯する", "料理する"]; //コメントにする
return (
<div className="bg-gray-100 flex justify-center items-center min-h-screen">
<Card className="w-[400px]">
<CardHeader>
<CardTitle>TODO App</CardTitle>
</CardHeader>
<CardContent>
<Input
placeholder="タスクを追加"
onChange={(e) => setTodo(e.target.value)}
/>
{/* <div>{todo}</div> */}
<Button
className="w-full mt-2"
onClick={() => {
setTodos([...todos, todo]);
}} // 追加
>
追加
</Button>
<ul>
{todos.map((todo) => (
<li className="bg-white p-2 mt-2 flex">
<div>・{todo}</div>
<button className="ml-2">削除する</button>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
);
}
テストデータをコメントにしました
// const todos = ["掃除する", "洗濯する", "料理する"];
追加ボタンをおすとtodosの内容が追加されて、画面の変更が行われるのでこういうものはuseState
を使って更新する必要があるので新たに用意します
const [todos, setTodos] = useState<string[]>([]);
ここでstring[]
としているのはtodosに入るデータの型を定義しています
todosにはstringの配列しか入らないことを書いています
初期値は[]なので、空配列になります
ボタンにonClick
を追加します
<Button
className="w-full mt-2"
onClick={() => {
setTodos([...todos, todo]);
}}
>
onClick
はボタンを押したときに実行する関数を書けます
setTodos([...todos, todo]);
このようにいま表示されているtodosの後ろにtodoを追加して更新しています
todosが["A" "B"]でCを追加したとしたらsetTodos(["A", "B", "C"])と同じになります
ここで以下の疑問が出た人がいるかと思います
setTodos(todos.push(todo));
いまあるtodosという配列にtodoを追加する実装です。
しかしこれでは画面に変化が起きません
useStateはセットされているものが変更されたかをみています
セットされたリストの中身が変更されているかはみていないのです
あくまでリストが変更されたことだけを注目しています
ここで追加ボタンを押してフォームをいちいち消すのが大変と気づくでしょう
追加ボタンを押したらtodoを空にする実装もしましょう
<Button
className="w-full mt-2"
onClick={() => {
setTodos([...todos, todo]);
setTodo("") // 追加
}}
>
削除機能
次に削除機能を作ります
削除するボタンを押したら削除するように実装します
{todos.map((todo, index) => (
<li className="bg-white p-2 mt-2 flex">
<div>・{todo}</div>
<button
className="ml-2"
onClick={() => {
setTodos(todos.filter((_, i) => i !== index));
}}
>
削除する
</button>
</li>
))}
まずはmapでインデックス番号(何番目のtodoか)をわかるようにします
todos.map((todo, index) => (
filter関数を使って、todosの番号と削除したいtodoのインデックスを比較して一致たものを配列から取り除きます(一致しないものだけを配列に残します)
onClick={() => {
setTodos(todos.filter((_, i) => i !== index));
}}
あとは削除するボタンが微妙なのでいい感じのアイコンにします
ここではreact-icons
を利用します
$ npm i react-icons
今回は以下のアイコンを利用します
https://react-icons.github.io/react-icons/search/#q=delete
import { useState } from "react";
import { Button } from "./components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "./components/ui/card";
import { Input } from "./components/ui/input";
import { MdDelete } from "react-icons/md"; //追加
export default function Home() {
const [todo, setTodo] = useState("");
const [todos, setTodos] = useState<string[]>([]);
// const todos = ["掃除する", "洗濯する", "料理する"];
return (
<div className="bg-gray-100 flex justify-center items-center min-h-screen">
<Card className="w-[400px]">
<CardHeader>
<CardTitle>TODO App</CardTitle>
</CardHeader>
<CardContent>
<Input
placeholder="タスクを追加"
onChange={(e) => setTodo(e.target.value)}
/>
{/* <div>{todo}</div> */}
<Button
className="w-full mt-2"
onClick={() => {
setTodos([...todos, todo]);
setTodo("");
}}
>
追加
</Button>
<ul>
{todos.map((todo, index) => (
<li className="bg-white p-2 mt-2 flex">
<div>・{todo}</div>
<button
className="ml-2"
onClick={() => {
setTodos(todos.filter((_, i) => i !== index));
}}
>
<MdDelete color="red" /> // 変更
</button>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
);
}
いい感じになりました
Firebaseへのデプロイ
アプリケーションができたのであとはユーザーに利用してもらうためにFirebaseを使ってデプロイを行います
アカウントが必要なので以下から作成して下さい
アカウントができると以下の画面にいくので、+ボタン(プロジェクトを追加)をクリックします
プロジェクト名は適当につけて大丈夫です
Googleアナリティクスを無効にして「プロジェクトを作成」をクリック
歯車マークからプロジェクトの設定をクリック
下まで移動して>マークをクリック
アプリのニックネームをmy-first-todo
にして「このアプリをFirebase Hostingも設定します」をチェックして「アプリを登録」をクリック
ここまでできたらターミナルに戻ってfirebaseのツールをインストールします
$ npm install firebase
$ npm install -g firebase-tools
$ firebase login
メール確認をしてください
$ firebase init hosting
? Please select an option: Use an existing project
? Select a default Firebase project for this directory: my-first-todo-6b04c (my-first-todo)
i Using project my-first-todo-6b04c (my-first-todo)
先ほど作成したプロジェクトを選んでください
? Detected an existing Vite codebase in the current directory, should we use this? (Y/n)
ここで必ずエンターを押してください
? In which region would you like to host server-side content, if applicable? asia-east1 (Taiwan)
台湾を選択
? Set up automatic builds and deploys with GitHub? (y/N) N
Noを選択
これでデプロイの設定が完了したので実施にデプロイを行います
$ firebase deploy
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/my-first-todo-6b04c/overview
Hosting URL: https://my-first-todo-6b04c.web.app
少し待つとURLが発行されるのでアクセスします
デプロイができました
このアプリはスマホからでもアクセスができるのでユーザー利用が可能になります
おわりに
今回はReactの基本を説明するためにTODOアプリを作りました
実際にユーザーに価値を届けるためにはデプロイも大切なので最後に行いました
このハンズオンでは細かいReactやJavaScriptの説明は省略していますので、気になる方はぜひ動画をみて作ってみてください
ここまで読んでいただけた方はいいねとストックよろしくお願いします。
@Sicut_study をフォローいただけるととてもうれしく思います。
また明日の記事でお会いしましょう!
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
実践的なカリキュラムで、あなたのエンジニアとしてのキャリアを最短で飛躍させましょう!
興味のある方は、ぜひホームページからお気軽にカウンセリングをお申し込みください!
▼▼▼