はじめに
Reactの人気はどんどん加速しています。これからフレームワークを学ぶならReactを選ぶのは賢い選択でしょう
こんにちは、Watanabe Jin(@Sicut_study)です。
今回はReactをこれから学んでいこうと思っている方に向けて、初心者チュートリアルを作成しました。
このチュートリアルではReactを書く上で欠かせないことを紹介しています。
実際にアプリを作りながらReactの機能について学ぶことによってより深く理解することが可能です。
このチュートリアルを最後までやったら、次に自分で習ったことを活かして簡単なTODOアプリなどを作れば基本がしっかり身につくようになっています。
またこのチュートリアルではJavaScriptではなくTypeScriptを採用しています。
モダンな会社ではTypeScriptが採用されることが多いはずだなので、少しでも慣れていただけるように解説していきます。
TypeScript自体についての理解が浅いという方は最初にこちらのチュートリアルをやっていただくとよりReactの本質的な部分に集中できます。
この記事の対象
この記事を最後まで実施することで簡単なReactのアプリケーションを作ることができるようになります。
- JavaScriptを経験したことがある人
- Reactをやってみたいという人
- 短い時間でしっかり学びたい人
- 手を動かして勉強するのが好きな人
- TypeScriptを使ってみたい人
動画で学びたい方
こちらのハンズオンは動画をご用意していますので、ご活用ください!
Reactとは?
Reactとは、Meta社(Facebook社)が開発したJavaScriptのフレームワークです。
ユーザーインターフェース(UI)を簡単に構築することができるようになります。
同じようなフレームワークとしてあげられるのが「Vue」や「Angular」ですが、npmのトレンドでは世界的にはReactがダントツで人気のフレームワークとなっています。
日本では「Vue」が人気の傾向がありましたが、最近「React」の人気がどんどん高まってきており、個人的な間隔としてはVueよりReactを選択するケースが増えてきているように感じます。
CAMELORS株式会社の調査では案件が多いフレームワーク1位にも輝きました
つまり今後学んでおくと役に立つ可能性の高いフレームワークだということがわかります。
私自信もプログラミングコーチングJISOUというサービスを運営していますが、ここでもReactを専門に指導をしています。それくらい将来性のあるフレームワークだと考えています。
Webサイトを作るとき、DOM(Document Object Model)をJavaScriptを使って操作することでユーザー操作で動きを変えていきます。
const element = document.querySelector();
element.classList.add();
element.addEventListener();
JavaScriptで操作をしているとコードはどんどん複雑になってしまいます。
そこでReactはコードが複雑になりにくい点や他のメリットが多くあることから利用されるようになりました。
1. 宣言的View
まずはJavaScriptとReactのコードを比較します。
以下はボタンをクリックするとアラートがでるような実装です。
JavaScript
function updateUI() {
const app = document.getElementById('app');
app.innerHTML = '<h1>Hello, World!</h1>';
}
document.getElementById('updateButton').addEventListener('click', updateUI);
React
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function App() {
const [message, setMessage] = useState('');
const updateUI = () => {
setMessage('Hello, World!');
};
return (
<div>
<div>{message}</div>
<button onClick={updateUI}>Update</button>
</div>
);
}
JavaScriptはDOM要素を手動で操作し、UIを更新します。UIの状態が変更されるたびに、手動でDOMを変更する必要があります
宣言的なコード(React)では、 状態が変更されると、自動的にUIが再レンダリングされます。UIの状態と見た目を直接対応させることで、コードがシンプルで読みやすくなります。
<div>
<div>{message}</div>
<button onClick={updateUI}>Update</button>
</div>
操作の部分が入っていないことですごくわかりやすいコードになっています。
このわかりやすさがメンテナンスのしやすさにつながってきます。
2. コンポーネントベース
ページを作るときにコンポーネントという要素を組み合わせます。
たとえば以下のページであればこのようなコンポーネント構成になっています。
ヘッダーの部分、バナーの部分などそれぞれがパーツになっており、それらを組み合わせることによって1枚のページが作成されています。
このようにコンポーネントというブロックを作ることによって、部品化ができるため改修がしやすかったり、再利用がしやすくなります。
3. 一度学習すれば、どこでも使える
Reactは一度使い方を覚えれば色々応用が効きます。モバイルアプリを作りたいなら「ReactNative」、ARなら「React360」など記述方法がほぼ同じで色々なものに技術が使えるのはMeta社のイチオシポイントのようです。
ここまででReactの特徴を紹介しましたが、Reactは仮想DOMを採用しているのも良いポイントです。
本来であればJavaScriptでDOMを変更するため操作をして、HTMLのDOMを新しく構築します。
仮想DOMを利用すると、DOMをコピーした仮想的なDOMでJavaScriptの変更をうけとって差分だけを変更して描画をしてくれます。つまりDOM全体を更新する必要がないのでパフォーマンスが上がります。
このように初心者WebエンジニアがReactを学ぶことは今後を考えても賢い選択だと思っています。
実際にこの記事ではReactのハンズオンをしながら抑えておきたい基本を紹介していきますので、学びながらReactの魅力を感じていきましょう
1. 環境構築
まずはReactを開発できる環境を用意しましょう。それにはJavaScriptを実行するためのNode.jsを動かせるようにする必要があります。
ダウンロードは以下のサイトから行ってください。
OSによってはインストールの方法も変わるため、調べれば簡単に導入することができます。
Reactの環境を作る方法はいくつかありますが、今回はViteを利用します。
Viteを使うことで環境構築が楽なだけでなくWebpackなどよりも早いアプリケーションになります。
$ npm -v
10.8.1
$ node -v
v22.4.0
$ npm create vite
✔ Project name: … react-beginner-tutorial
✔ Select a framework: › React
✔ Select a variant: › TypeScript
$ cd react-beginner-tutorial
$ npm i // ライブラリをインストール
$ npm run dev // サーバーを起動する
http://localhost:5173にアクセスして以下の画面が出ればReactの環境を作ることができたことがわかります。
ではこのディレクトリをVSCodeで開きましょう
ここからはReact開発で必須の「Prettier」という拡張機能を入れていきます。
Prettierを入れることによってコードのインデントなどを直してくれるようになります。
Prettierをインストールしたら、設定を開いて
「format on save」と検索して、チェックをいれましょう
もう1つ拡張機能をいれます。「ES7+React/Redux/React-Native snippets」と検索して入れてください。
2. ディレクトリ構成について
色々ファイルがありますが、ここでは初心者が知っておきたいものを説明していきます。
src/App.tsx
はReactの心臓部分のようなファイルで、ここからUIの構築やルーティングなどを記述してアプリケーションを構築していきます。
src/main.tsx
はReactアプリケーションの「スタート地点」です。このファイルは、App.tsxで定義されたアプリケーション全体をブラウザに表示するための設定を行います。
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
index.html
ではmain.tsx
を読み込んでいます。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
src/main.tsx
では以下のような記述があります
ReactDOM.createRoot(document.getElementById('root')!).render(
ここでHTMLの
<body>
<div id="root"></div>
この部分でApp.tsxのレンダリングが行われています。
package.json
は、Node.jsプロジェクト(Reactアプリを含む)における設定ファイルです。このファイルは、プロジェクトに関する基本的な情報や、依存しているライブラリ、スクリプトなどを管理します。
tsconfig.json
はTypeScriptプロジェクトの設定ファイルです。このファイルは、TypeScriptコンパイラに対して、どのようにコードを解釈し、変換するかを指示します。
この設定を変更することでより厳密なコードを書くことも可能になります。
3. コンポーネントを作成する
今回は簡単な家計簿アプリを作りながらReactについて学んでいきます。
まずはじめはページを構成する部品であるコンポーネントをやっていきます。
src
の下にTitle.tsx
を作成します。Reactのコンポーネントを作るにはtsx
というファイル形式で作る必要があります。
$ touch src/Title.tsx
コンポーネントは以下のように作成します。
export const Title = () => {
return (
<div>家計簿アプリ</div>
)
}
このように書くことで、<Title />
タグでタイトルを表示できるようになります。
作成したコンポーネントをApp.tsxでインポートして、コンポーネントを利用してみましょう
import './App.css'
import { Title } from './Title'
function App() {
return (
<div>
<Title />
</div>
)
}
export default App
次に不要なスタイルを消しておきます
index.css
の中身をすべて消してください (左寄りに表示されてしまいます)
実際に画面をみると「家計簿アプリ」と表示されるようになりました
では、再利用してみましょう
import './App.css'
import { Title } from './Title'
function App() {
return (
<div>
<Title />
<Title />
<Title />
</div>
)
}
export default App
再利用もすごく簡単にできました!
HTML部分にはテンプレートリテラルも利用できます。
export const Title = () => {
const name = "Yamada"
return (
<div>{name}の家計簿アプリ</div>
)
}
4. ライブラリの導入
まずはフレームワークとライブラリの違いをはっきりさせましょう
フレームワークとは簡単に言うと、デフォルトでアプリケーション開発に必要なものが揃っているもの
ライブラリとは作業を簡単にするためのあらかじめ用意されたプログラムの集まり
ということができます。
今回はライブラリの中でもコンポーネントライブラリである「ChakraUI」を導入します。
ChakuraUIを利用するとリッチなUIを簡単な記述で使えるようになりますので、初心者でもいい感じのWebサイトを簡単に作ることができるようになります。
$ npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
src/App.tsx
にChakraUIを使う設定をします。
import { ChakraBaseProvider } from '@chakra-ui/react'
import './App.css'
import { Title } from './Title'
function App() {
return (
<ChakraBaseProvider>
<div>
<Title />
<Title />
<Title />
</div>
</ChakraBaseProvider>
)
}
export default App
ChakraProvider
をインポートして、HTMLの外側を覆うようにしました
このタグの内側にあるものはChakraUIを利用することができるようになります。
正しく導入できたかボタンを表示して確認してみましょう
<Butto
と打つとVSCodeが補完を出してくれるので、Tabキー
を押してみましょう
自動でボタンコンポーネントのインポートまでしてくれます
import { Button, ChakraProvider } from '@chakra-ui/react'
import './App.css'
import { Title } from './Title'
function App() {
return (
<ChakraProvider>
<div>
<Title />
<Title />
<Title />
<Button colorScheme="blue">ボタン</Button>
</div>
</ChakraProvider>
)
}
export default App
リッチなボタンが表示できました。
<Button colorScheme="blue">ボタン</Button>
ボタンコンポーネントにはcolorScheme
というプロパティに値を渡すことで見た目を変更できます
ChakraUIを使うのであればドキュメントをみてどのようにカスタマイズできるのかをみるようにしましょう!
では実際にText
コンポーネントを使ってアプリ名を表示しましょう(私達の作ったコンポーネントとはお別れです)
import {ChakraProvider, Text } from '@chakra-ui/react'
import './App.css'
function App() {
return (
<ChakraProvider>
<div>
<Text fontSize="2xl">家計簿アプリ</Text>
</div>
</ChakraProvider>
)
}
export default App
サイズは2xl
に設定しました
タイトルをつけることができたので、ここから支出を入力できるようなコンポーネントを作成していきます!
5. 入力フォームの作成
まずはじめに支出の記録をするための入力フォームと追加ボタンを作成していきます。
import { Button, ChakraProvider, Checkbox, Flex, Input, Text } from '@chakra-ui/react'
import './App.css'
function App() {
return (
<ChakraProvider>
<div>
<Text fontSize="2xl">家計簿アプリ</Text>
<div>
<Input placeholder='支出を入力' mb="4px" />
<Flex align="center" justifyContent="space-between">
<Checkbox w="100px">入金</Checkbox>
<Button colorScheme="teal">追加</Button>
</Flex>
</div>
</div>
</ChakraProvider>
)
}
export default App
このアプリでは支出を記録しますが、もし給料などお金が手元に入ったときには入金をチェックすることでプラスで計算できるようんします。
入力フォームは以下のコンポーネントを利用しました
入金のチェックボックスはこちらを利用しています
入金のチェックボックスと追加ボタンを横並びにしたいのでdisploy: flex
をする必要があります。
ChakaUIには簡単にflex
ができるコンポーネントがあるので使いました
<Flex align="center" justifyContent="space-between">
align="center"
で縦に中央揃え、justifyContent="space-between"
で左右に均等に配置しました
これらのプロパティは<Flex>
に用意されているものでドキュメントをみると何が使えるのかを確認できます
<Input placeholder='支出を入力' mb="4px" />
このmb="4px"
もマージンを下に4pxあけるプロパティで、こちらも用意されているものを使っています。
6. リストの表示
では次にテストデータを用意して支出を表示ていきましょう
import { Box, Button, ChakraProvider, Checkbox, Flex, Input, Text } from '@chakra-ui/react'
import './App.css'
function App() {
const testData = [
// 支出と入金のデータを作成
{
id: 1,
title: 'お金を払う',
isIncome: false,
amount: 1000,
},
{
id: 2,
title: 'お金をもらう',
isIncome: true,
amount: 1000,
},
]
return (
<ChakraProvider>
<div>
<Text fontSize="2xl">家計簿アプリ</Text>
<Box mb="8px">
<Input placeholder='支出を入力' mb="4px" />
<Flex align="center" justifyContent="space-between">
<Checkbox w="100px">入金</Checkbox>
<Button colorScheme="teal">追加</Button>
</Flex>
</Box>
<div>
{testData.map((data) => (
<div key={data.id}>
<Flex align="center" justifyContent="space-between">
<Text>{data.title}</Text>
<Text>{data.isIncome ? "+" : "-"}{data.amount}</Text>
</Flex>
</div>
))}
</div>
</div>
</ChakraProvider>
)
}
export default App
まずはテストデータを用意しました
支出と入金があるため、どちらかわかるようにisIncome
という項目を用意しました。
true
なら入金、false
なら支出を表現しています
let testData = [
// 支出と入金のデータを作成
{
id: 1,
title: 'お金を払う',
isIncome: false,
amount: 1000,
},
{
id: 2,
title: 'お金をもらう',
isIncome: true,
amount: 1000,
},
]
入力フォームとリストの表示の間にマージンをあけたかったので、div
からBox
にタグを変えました
意味合いはあまり変わりませんが、BoxはChakraUIが用意しているコンポーネントなのでmb
のようなプロパティが使えるのでCSSを書かないでスタイリングができます
<Box mb="8px">
<Input placeholder='支出を入力' mb="4px" />
<Flex align="center" justifyContent="space-between">
<Checkbox w="100px">入金</Checkbox>
<Button colorScheme="teal">追加</Button>
</Flex>
</Box>
リストの表示は以下のように書きました
<div>
{testData.map((data) => (
<div key={data.id}>
<Flex align="center" justifyContent="space-between">
<Text>{data.title}</Text>
<Text>{data.isIncome ? "+" : "-"}{data.amount}</Text>
</Flex>
</div>
))}
</div>
すこしわかりづらいですがmap
を使っているため展開すると以下のようになります
<div>
<div key={testData[0].id}>
<Flex align="center" justifyContent="space-between">
<Text>{testData[0].title}</Text>
<Text>{testData[0].isIncome ? "+" : "-"}{testData[0].amount}</Text>
</Flex>
</div>
<div key={testData[1].id}>
<Flex align="center" justifyContent="space-between">
<Text>{testData[1].title}</Text>
<Text>{testData[1].isIncome ? "+" : "-"}{testData[1].amount}</Text>
</Flex>
</div>
</div>
支出か入金かを表すために3項演算子を使って、isIncomeなら+、それ以外なら-を表示しました
<Text>{testData[1].isIncome ? "+" : "-"}{testData[1].amount}</Text>
ここで疑問に思うのはkey
の存在です
<div key={data.id}>
リストの表示をするときにはkey
という項目に一意の値を入れる必要があります。
これはReact がどのアイテムが変更、追加、または削除されたかを識別するのに役立ちます。
もしkey
がない場合、このリストに変更が加わったときにどの要素が変更されたのかがわからず、支出の記録の一部を再描画するのではなく、全体を再描画することに繋がりパフォーマンスが落ちてしまいます
また、変な挙動を引き起こす原因にもなってしまいます。
簡単な例を表示します。
このアプリケーションはkey
を指定しないで作成しました。
使用したコード
import { useState } from 'react'
type Item = { value: string }[]
const App = () => {
const [inputValue, setInputValue] = useState('')
const [list, setList] = useState<Item>([])
const addToList = () => {
setList((prevList) => {
return [{ value: inputValue }, ...prevList]
})
setInputValue('')
}
return (
<>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={addToList}>追加</button>
<ul>
{list.map((item) => {
return <li>{item.value}</li>
})}
</ul>
</>
)
}
export default App
リストに追加をするとul
の中のすべてのli
が更新されていることがわかります。
Keyを指定することで、追加した部分だけを更新することができるようになります。
最初は難しいと感じると思うのでmap
を使ったならkey
を一意なもので指定しておくことを覚えておけばよいでしょう。
7. イベントハンドラ
リストが表示できたので次は実際にインプットフォームに入力をして追加できるようんしましょう
ReactにはイベントハンドラというDom要素を処理してくれる便利な機能があります。
例えば、追加ボタンをクリックしたらコンソールにHello Worldを出すようにしてみましょう
<Button colorScheme="teal" onClick={ () => console.log("Hello World")}>追加</Button>
onClick
を利用することでイベントを設定した要素をクリックしたときに関数を実行することができます。
インプットフォームなど入力する要素に対してはonChange
が利用できます
<Input placeholder='支出を入力' mb="4px" onChange={(e) => console.log(e.target.value)} />
onChangeでは(e) =>
と書くことでイベントを受け取ることができます。
イベントの中から変更した値を受け取るにはe.target.value
とすることでインプットフォームの入力内容を表示することが可能です
1文字打つたびに変更されるのでonChnage
が動いて値が表示されます。
ではこれらを用いて追加ボタンを押したら記録データのオブジェクトをコンソールに表示するようにしましょう
タイトルの入力フォームもついでに用意します
import { Box, Button, ChakraProvider, Checkbox, Flex, Input, Text } from '@chakra-ui/react'
import './App.css'
function App() {
let testData = [
// 支出と入金のデータを作成
{
id: 1,
title: 'お金を払う',
isIncome: false,
amount: 1000,
},
{
id: 2,
title: 'お金をもらう',
isIncome: true,
amount: 1000,
},
]
let title: string = ''
let amount: number = 0
let isIncome: boolean = false
return (
<ChakraProvider>
<div>
<Text fontSize="2xl">家計簿アプリ</Text>
<Box mb="8px">
<Input placeholder='タイトルを入力' mb="4px" onChange={(e) => title = e.target.value} />
<Input placeholder='支出を入力' mb="4px" onChange={(e) => amount = Number(e.target.value)} />
<Flex align="center" justifyContent="space-between">
<Checkbox w="100px" onChange={() => isIncome = !isIncome}>入金</Checkbox>
<Button colorScheme="teal" onClick={ () => console.log(
title,
amount,
isIncome
)}>追加</Button>
</Flex>
</Box>
<div>
{testData.map((data) => (
<div key={data.id}>
<Flex align="center" justifyContent="space-between">
<Text>{data.title}</Text>
<Text>{data.isIncome ? "+" : "-"}{data.amount}</Text>
</Flex>
</div>
))}
</div>
</div>
</ChakraProvider>
)
}
export default App
入力して追加ボタンを押すことでコンソールに表示ができました。
では、これをテストデータに追加するようにしてみましょう
<Button colorScheme="teal" onClick={ () => testData.push({ id: 999, "title": title, "isIncome": isIncome, "amount": amount})}>追加</Button>
しかし、追加ボタンを押しても新しい支出は表示されません
DOM要素を変更して再描画したい場合には、ステートを利用する必要があります。
次はこちらを説明していきます。
8. ステート
画面に表示されているデータなどを変更する場合にはuseState
というフック(React の機能を接続するための関数)を使います。
実際に使うほうがイメージが付きやすいのでまずは利用したコードから載せます
import { Box, Button, ChakraProvider, Checkbox, Flex, Input, Text } from '@chakra-ui/react'
import './App.css'
import { useState } from 'react'
type Record = {
id: number
title: string
isIncome: boolean
amount: number
}
function App() {
const [records, setRecords] = useState<Record[]>([])
const [title, setTitle] = useState<string>('')
const [isIncome, setIsIncome] = useState<boolean>(false)
const [amount, setAmount] = useState<number>(0)
return (
<ChakraProvider>
<div>
<Text fontSize="2xl">家計簿アプリ</Text>
<Box mb="8px">
<Input placeholder='タイトルを入力' mb="4px" onChange={(e) => setTitle(e.target.value)} />
<Input placeholder='支出を入力' mb="4px" onChange={(e) => setAmount(Number(e.target.value))} />
<Flex align="center" justifyContent="space-between">
<Checkbox w="100px" onChange={() => setIsIncome(!isIncome)}>入金</Checkbox>
<Button colorScheme="teal" onClick={() => setRecords([...records, {id: records.length + 1, "title": title, "isIncome": isIncome, "amount": amount}])}>追加</Button>
</Flex>
</Box>
<div>
{records.map((data) => (
<div key={data.id}>
<Flex align="center" justifyContent="space-between">
<Text>{data.title}</Text>
<Text>{data.isIncome ? "+" : "-"}{data.amount}</Text>
</Flex>
</div>
))}
</div>
</div>
</ChakraProvider>
)
}
export default App
これで実際に追加ができるようになりました
では詳しく説明していきます。
まずはuseState
の部分です
const [records, setRecords] = useState<Record[]>([])
const [title, setTitle] = useState<string>('')
const [isIncome, setIsIncome] = useState<boolean>(false)
const [amount, setAmount] = useState<number>(0)
useStateはこのように書くことができてrecordsがステート、setRecordsがステートの値を更新する関数になります。ステートが更新された場合もし画面に表示されているのであれば描画が行われます。
タイトルや金額もステートを使って管理するようにしました
例えばタイトルが更新されたら以下のようにステートを更新します
<Input placeholder='タイトルを入力' mb="4px" onChange={(e) => setTitle(e.target.value)} />
チェックボックスはtrue/falseなので、デフォルト値をfalse
にして、チェックされたら逆を現在のisIncome
の逆の値で更新するようにしています
const [isIncome, setIsIncome] = useState<boolean>(false) // 初期値 false
<Checkbox w="100px" onChange={() => setIsIncome(!isIncome)}>入金</Checkbox>
次にrecords
の更新ですが、
<Button colorScheme="teal" onClick={() => setRecords([...records, {id: records.length + 1, "title": title, "isIncome": isIncome, "amount": amount}])}>追加</Button>
すでにあるrecords
を展開してその後ろに新しいrecordを追加して新しい配列を作成しています。
ここで疑問に思った人もいるかと思います。
<Button colorScheme="teal" onClick={() => setRecords.push({id: records.length + 1, "title": title, "isIncome": isIncome, "amount": amount})}>追加</Button>
このようにもとの配列に対して最後にpush
すればよいように思えます。
しかしこれではステートの更新が行われません。ステートの更新には新しい変数を与える必要があります。
もしpush
にしてしまうと配列の中身まではuseStateでは気にしないので(変数のアドレスは同じ)、配列自体が更新されてないと勘違いされてしまうのです。
配列の更新をする場合は展開をするということを覚えておきましょう
最後にいちいち追加するたびにタイトルと金額を消さないといけないのが大変なので直していきましょう
import {
Button,
ChakraProvider,
Checkbox,
Flex,
Input,
Text,
} from "@chakra-ui/react";
import { useState } from "react";
type Record = {
id: number;
title: string;
amount: number;
isIncome: boolean;
};
function App() {
const [records, setRecords] = useState<Record[]>([]);
const [title, setTitle] = useState<string>("");
const [amount, setAmount] = useState<number>(0);
const [isIncome, setIsIncome] = useState<boolean>(false);
return (
<ChakraProvider>
<div>
<Text fontSize="2xl">家計簿アプリ</Text>
<div>
<Input
placeholder="タイトルを入力"
mb="4px"
onChange={(e) => setTitle(e.target.value)}
value={title}
/>
<Input
placeholder="金額を入力"
mb="4px"
type="number"
onChange={(e) => setAmount(Number(e.target.value))}
value={amount}
/>
<Flex align="center" justifyContent="space-between">
<Checkbox
onChange={() => setIsIncome(!isIncome)}
isChecked={isIncome}
>
入金
</Checkbox>
<Button
colorScheme="blue"
onClick={() => {
const newRecord: Record = {
id: records.length + 1,
title: title,
amount: amount || 0,
isIncome: isIncome,
};
setRecords([...records, newRecord]);
setTitle("");
setAmount(0);
setIsIncome(false);
}}
>
追加
</Button>
</Flex>
</div>
<div>
{records.map((record) => (
<div key={record.id}>
<Flex align="center" justifyContent="space-between">
<Text>{record.title}</Text>
<Text>
{record.isIncome ? "+" : "-"}
{record.amount}
</Text>
</Flex>
</div>
))}
</div>
</div>
</ChakraProvider>
);
}
export default App;
インプットフォームにvalue
を設定することで現在のステートをインプットの入力として入れてくれます
<Input
placeholder="金額を入力"
mb="4px"
type="number"
onChange={(e) => setAmount(Number(e.target.value))}
value={amount}
/>
そして追加ボタンを押したらtitle``amount``isIncome
を初期値に更新することでフォームから消せます
<Button
colorScheme="blue"
onClick={() => {
const newRecord: Record = {
id: records.length + 1,
title: title,
amount: amount || 0,
isIncome: isIncome,
};
setRecords([...records, newRecord]);
setTitle("");
setAmount(0);
setIsIncome(false);
}}
>
追加
</Button>
追加ボタンのところが大きくなってきたので、追加の処理を関数に切り分けてあげましょう
import {
Button,
ChakraProvider,
Checkbox,
Flex,
Input,
Text,
} from "@chakra-ui/react";
import { useState } from "react";
type Record = {
id: number;
title: string;
amount: number;
isIncome: boolean;
};
function App() {
const [records, setRecords] = useState<Record[]>([]);
const [title, setTitle] = useState<string>("");
const [amount, setAmount] = useState<number>(0);
const [isIncome, setIsIncome] = useState<boolean>(false);
const addRecord = () => {
const newRecord: Record = {
id: records.length + 1,
title: title,
amount: amount || 0,
isIncome: isIncome,
};
setRecords([...records, newRecord]);
setTitle("");
setAmount(0);
setIsIncome(false);
};
return (
<ChakraProvider>
<div>
<Text fontSize="2xl">家計簿アプリ</Text>
<div>
<Input
placeholder="タイトルを入力"
mb="4px"
onChange={(e) => setTitle(e.target.value)}
value={title}
/>
<Input
placeholder="金額を入力"
mb="4px"
type="number"
onChange={(e) => setAmount(Number(e.target.value))}
value={amount}
/>
<Flex align="center" justifyContent="space-between">
<Checkbox
onChange={() => setIsIncome(!isIncome)}
isChecked={isIncome}
>
入金
</Checkbox>
<Button colorScheme="blue" onClick={addRecord}>
追加
</Button>
</Flex>
</div>
<div>
{records.map((record) => (
<div key={record.id}>
<Flex align="center" justifyContent="space-between">
<Text>{record.title}</Text>
<Text>
{record.isIncome ? "+" : "-"}
{record.amount}
</Text>
</Flex>
</div>
))}
</div>
</div>
</ChakraProvider>
);
}
export default App;
addRecordという関数に処理を移動しました
const addRecord = () => {
const newRecord: Record = {
id: records.length + 1,
title: title,
amount: amount || 0,
isIncome: isIncome,
};
setRecords([...records, newRecord]);
setTitle("");
setAmount(0);
setIsIncome(false);
};
そしてハンドラでは関数を呼び出すだけになってとても読みやすくなりました
<Button colorScheme="blue" onClick={addRecord}>
追加
</Button>
9. useEffect
最後にReactで必ず利用するuseEffect
を紹介します。
useEffect
とはコンポーネントを外部システムと同期させるための React フックです。
画面を表示する前に外部のAPIなどを叩いて記録データを取得して、描画前に用意することで、画面が表示されたときには外部のDBにある支出データを事前に表示することができるようになります。
ここでは簡易的なモックサーバーをjson-serverを使って作成していきます。
$ npm i json-server
$ touch db.json
db.jsonに今回サーバーから返すjsonを定義します
{
"records": [
{
"id": 1,
"title": "お金を払う",
"isIncome": false,
"amount": 1000
},
{
"id": 2,
"title": "お金をもらう",
"isIncome": true,
"amount": 2000
}
]
}
ではモックサーバーを起動しましょう
package.jsonというところにコマンドを追加します
この設定をすることでnpm run json-server
を動かすとjson-server --watch db.json
を実行することができます
{
"name": "typescript-beginner-tutorial",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"json-server": "json-server --watch db.json" // 追加
},
"dependencies": {
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"framer-motion": "^11.3.24",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"typescript": "^5.2.2",
"vite": "^5.3.4"
}
}
$ npm run json-server
$ curl localhost:3000/records
[
{
"id": "1",
"title": "お金を払う",
"isIncome": false,
"amount": 1000
},
{
"id": "2",
"title": "お金をもらう",
"isIncome": true,
"amount": 2000
}
]j
http://localhost:3000/recordsにcurlするとjsonが返ってきました
このデータをアプリを開いたら取得して、records
の初期値にしてきます。
import { Box, Button, ChakraProvider, Checkbox, Flex, Input, Text } from '@chakra-ui/react'
import './App.css'
import { useEffect, useState } from 'react'
type Record = {
id: number
title: string
isIncome: boolean
amount: number
}
function App() {
const [records, setRecords] = useState<Record[]>([])
const [title, setTitle] = useState<string>('')
const [isIncome, setIsIncome] = useState<boolean>(false)
const [amount, setAmount] = useState<number>(0)
useEffect(() => {
getRecords()
async function getRecords() {
const response = await fetch('http://localhost:3000/records')
const data = await response.json()
setRecords(data)
}
}, [])
return (
<ChakraProvider>
<div>
<Text fontSize="2xl">家計簿アプリ</Text>
<Box mb="8px">
<Input placeholder='タイトルを入力' mb="4px" onChange={(e) => setTitle(e.target.value)} />
<Input placeholder='支出を入力' mb="4px" onChange={(e) => setAmount(Number(e.target.value))} />
<Flex align="center" justifyContent="space-between">
<Checkbox w="100px" onChange={() => setIsIncome(!isIncome)}>入金</Checkbox>
<Button colorScheme="teal" onClick={() => setRecords([...records, {id: records.length + 1, "title": title, "isIncome": isIncome, "amount": amount}])}>追加</Button>
</Flex>
</Box>
<div>
{records.map((data) => (
<div key={data.id}>
<Flex align="center" justifyContent="space-between">
<Text>{data.title}</Text>
<Text>{data.isIncome ? "+" : "-"}{data.amount}</Text>
</Flex>
</div>
))}
</div>
</div>
</ChakraProvider>
)
}
export default App
アプリを開くとサーバーから取得した値を初期値にしています。
ではuseEffect
の実装を見てみましょう
useEffect(() => {
getRecords()
async function getRecords() {
const response = await fetch('http://localhost:3000/records')
const data = await response.json()
setRecords(data)
}
}, [])
useEffectの中ではasync直接呼び出せないので、非同期でデータを取得する関数を定義して関数を呼び出すようにしています。
データを取得したらrecords
の初期値としてsetRecords
を使ってデータを入れています。
useEffect(() => {
ここに画面描画前にやりたい処理
}, []
この書き方を覚えておけば初心者の方は大丈夫です
[]
も意味があるので、そこは気になる方は調べてみてください
これにて家計簿アプリは完成です🎉🎉
おわりに
今回はReactをやる中で絶対に抑えておきたい頻出のスキルを一通りハンズオンを通して学習しました
この内容があればTODOアプリなどを作れるスキルが身についているはずです。
ぜひともここで習ったことを活かしてアプリを作ってみてください。自分で実際につかうことでより理解を深めることが可能です。
今回の内容は以下の動画でも学べますのでより詳しく知りたい方はぜひご覧ください
ここまで読んでいただけた方はいいねとストックよろしくお願いします。
@Sicut_study をフォローいただけるととてもうれしく思います。
普段はTwitterでエンジニアに関する情報を発信していますのでよければ友達になってください👇
また明日の記事でお会いしましょう!
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページからお気軽にご連絡ください!
▼▼▼