【完全版】これ1本でReactの基本がマスターできる！初心者チュートリアル！


![Qiitaサムネイル (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/3b7e29aa-e7ca-7f95-c755-50a6193a6da7.png)

# はじめに

**Reactの人気はどんどん加速しています。これからフレームワークを学ぶならReactを選ぶのは賢い選択でしょう**

こんにちは、Watanabe Jin(@Sicut_study)です。
今回はReactをこれから学んでいこうと思っている方に向けて、初心者チュートリアルを作成しました。
このチュートリアルではReactを書く上で欠かせないことを紹介しています。

実際にアプリを作りながらReactの機能について学ぶことによってより深く理解することが可能です。
このチュートリアルを最後までやったら、次に自分で習ったことを活かして簡単なTODOアプリなどを作れば基本がしっかり身につくようになっています。

またこのチュートリアルではJavaScriptではなくTypeScriptを採用しています。
モダンな会社ではTypeScriptが採用されることが多いはずだなので、少しでも慣れていただけるように解説していきます。

TypeScript自体についての理解が浅いという方は最初にこちらのチュートリアルをやっていただくとよりReactの本質的な部分に集中できます。

https://qiita.com/Sicut_study/items/340022c2a7bc05b15587

# この記事の対象

この記事を最後まで実施することで簡単なReactのアプリケーションを作ることができるようになります。

- JavaScriptを経験したことがある人
- Reactをやってみたいという人
- 短い時間でしっかり学びたい人
- 手を動かして勉強するのが好きな人
- TypeScriptを使ってみたい人

# 動画で学びたい方

こちらのハンズオンは動画をご用意していますので、ご活用ください！

https://youtu.be/_S--bTdPezU?si=g3ehpB1ofW5IE1lU

# Reactとは？

Reactとは、Meta社(Facebook社)が開発したJavaScriptのフレームワークです。
ユーザーインターフェース(UI)を簡単に構築することができるようになります。

同じようなフレームワークとしてあげられるのが「Vue」や「Angular」ですが、npmのトレンドでは世界的にはReactがダントツで人気のフレームワークとなっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/f375d721-d262-d659-f79d-5513f874626a.png)

https://npmtrends.com/angular-vs-react-vs-vue

日本では「Vue」が人気の傾向がありましたが、最近「React」の人気がどんどん高まってきており、個人的な間隔としてはVueよりReactを選択するケースが増えてきているように感じます。

CAMELORS株式会社の調査では**案件が多いフレームワーク1位にも輝きました**

https://prtimes.jp/main/html/rd/p/000000111.000045678.html

つまり今後学んでおくと役に立つ可能性の高いフレームワークだということがわかります。
私自信もプログラミングコーチングJISOUというサービスを運営していますが、ここでもReactを専門に指導をしています。それくらい将来性のあるフレームワークだと考えています。

Webサイトを作るとき、DOM(Document Object Model)をJavaScriptを使って操作することでユーザー操作で動きを変えていきます。

```js
const element = document.querySelector();
element.classList.add();
element.addEventListener();
```

JavaScriptで操作をしているとコードはどんどん複雑になってしまいます。
そこでReactはコードが複雑になりにくい点や他のメリットが多くあることから利用されるようになりました。

### 1. 宣言的View

まずはJavaScriptとReactのコードを比較します。
以下はボタンをクリックするとアラートがでるような実装です。

**JavaScript**
```js
        function updateUI() {
            const app = document.getElementById('app');
            app.innerHTML = '<h1>Hello, World!</h1>';
        }

        document.getElementById('updateButton').addEventListener('click', updateUI);
```

**React**

```js
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の状態と見た目を直接対応させることで、コードがシンプルで読みやすくなります。

```js
        <div>
            <div>{message}</div>
            <button onClick={updateUI}>Update</button>
        </div>
```

操作の部分が入っていないことですごくわかりやすいコードになっています。
このわかりやすさがメンテナンスのしやすさにつながってきます。

### 2. コンポーネントベース

ページを作るときにコンポーネントという要素を組み合わせます。
たとえば以下のページであればこのようなコンポーネント構成になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/3d2b1127-017d-e535-589a-96a0e2eef508.png)

ヘッダーの部分、バナーの部分などそれぞれがパーツになっており、それらを組み合わせることによって1枚のページが作成されています。

このようにコンポーネントというブロックを作ることによって、部品化ができるため改修がしやすかったり、再利用がしやすくなります。

### 3. 一度学習すれば、どこでも使える

Reactは一度使い方を覚えれば色々応用が効きます。モバイルアプリを作りたいなら「ReactNative」、ARなら「React360」など記述方法がほぼ同じで色々なものに技術が使えるのはMeta社のイチオシポイントのようです。

https://ja.legacy.reactjs.org/


ここまででReactの特徴を紹介しましたが、Reactは仮想DOMを採用しているのも良いポイントです。
本来であればJavaScriptでDOMを変更するため操作をして、HTMLのDOMを新しく構築します。

仮想DOMを利用すると、DOMをコピーした仮想的なDOMでJavaScriptの変更をうけとって差分だけを変更して描画をしてくれます。つまりDOM全体を更新する必要がないのでパフォーマンスが上がります。

このように初心者WebエンジニアがReactを学ぶことは今後を考えても賢い選択だと思っています。
実際にこの記事ではReactのハンズオンをしながら抑えておきたい基本を紹介していきますので、学びながらReactの魅力を感じていきましょう

# 1. 環境構築

まずはReactを開発できる環境を用意しましょう。それにはJavaScriptを実行するためのNode.jsを動かせるようにする必要があります。

ダウンロードは以下のサイトから行ってください。

https://nodejs.jp/

OSによってはインストールの方法も変わるため、調べれば簡単に導入することができます。

Reactの環境を作る方法はいくつかありますが、今回はViteを利用します。

https://ja.vitejs.dev/

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](http://localhost:5173)にアクセスして以下の画面が出ればReactの環境を作ることができたことがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/2a91384e-3eb0-17fe-6989-007a3e9345c4.png)

ではこのディレクトリをVSCodeで開きましょう
ここからはReact開発で必須の「Prettier」という拡張機能を入れていきます。
Prettierを入れることによってコードのインデントなどを直してくれるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/e9106c6a-1238-fb5d-0802-95adf9a799ce.png)

Prettierをインストールしたら、設定を開いて

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/8a07fbd5-1948-49bc-3897-392856551f1e.png)

「format on save」と検索して、チェックをいれましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/488b64c4-e697-aaaa-81ef-c61aa7333a48.png)

もう1つ拡張機能をいれます。「ES7+React/Redux/React-Native snippets」と検索して入れてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/1ae6c5f9-6ea7-e173-2c9e-88d1dee8d590.png)


## 2. ディレクトリ構成について

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/1513bc47-3d16-dcbe-2ea0-c1df7b4a0900.png)

色々ファイルがありますが、ここでは初心者が知っておきたいものを説明していきます。

`src/App.tsx` はReactの心臓部分のようなファイルで、ここからUIの構築やルーティングなどを記述してアプリケーションを構築していきます。

`src/main.tsx`はReactアプリケーションの「スタート地点」です。このファイルは、App.tsxで定義されたアプリケーション全体をブラウザに表示するための設定を行います。

```tsx:src/main.tsx
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)
```

`index.html`では`main.tsx`を読み込んでいます。

```html
<!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`では以下のような記述があります

```tsx
ReactDOM.createRoot(document.getElementById('root')!).render(
```

ここでHTMLの

```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
```

コンポーネントは以下のように作成します。

```tsx:Title.tsx
export const Title = () => {
  return (
    <div>家計簿アプリ</div>
  )
}
```

このように書くことで、`<Title />`タグでタイトルを表示できるようになります。
作成したコンポーネントをApp.tsxでインポートして、コンポーネントを利用してみましょう

```tsx:App.tsx
import './App.css'
import { Title } from './Title'

function App() {
  return (
    <div>
      <Title />
    </div>
  )
}

export default App
```

次に不要なスタイルを消しておきます
`index.css`の中身をすべて消してください (左寄りに表示されてしまいます)

実際に画面をみると「家計簿アプリ」と表示されるようになりました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/4ce8fc24-8963-1e86-58f9-4efe77a1ef49.png)

では、再利用してみましょう

```tsx:App.tsx
import './App.css'
import { Title } from './Title'

function App() {
  return (
    <div>
      <Title />
      <Title />
      <Title />
    </div>
  )
}

export default App
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/b6e0f419-74f8-528b-16a4-f1b19f9d3b4d.png)


再利用もすごく簡単にできました！

HTML部分にはテンプレートリテラルも利用できます。

```tsx:Title.tsx
export const Title = () => {

  const name = "Yamada"
  return (
    <div>{name}の家計簿アプリ</div>
  )
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/f83d55ed-35b6-a162-9e80-a6fecc3b6a2a.png)

# 4. ライブラリの導入

まずはフレームワークとライブラリの違いをはっきりさせましょう

フレームワークとは簡単に言うと、デフォルトでアプリケーション開発に必要なものが揃っているもの
ライブラリとは作業を簡単にするためのあらかじめ用意されたプログラムの集まり

ということができます。

https://zenn.dev/nameless_sn/articles/framework_and_library

今回はライブラリの中でもコンポーネントライブラリである「ChakraUI」を導入します。
ChakuraUIを利用するとリッチなUIを簡単な記述で使えるようになりますので、初心者でもいい感じのWebサイトを簡単に作ることができるようになります。

https://v2.chakra-ui.com/

```
$ npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
```

`src/App.tsx`にChakraUIを使う設定をします。

```tsx:App.tsx
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キー`を押してみましょう
自動でボタンコンポーネントのインポートまでしてくれます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/cb8d99ac-b057-88f9-b8f2-f843b30e3539.png)

```tsx:App.tsx
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/e4305065-da62-09d4-6701-8a371167f018.png)

リッチなボタンが表示できました。

```tsx
<Button colorScheme="blue">ボタン</Button>
```

ボタンコンポーネントには`colorScheme`というプロパティに値を渡すことで見た目を変更できます

https://v2.chakra-ui.com/docs/components/button/usage

ChakraUIを使うのであればドキュメントをみてどのようにカスタマイズできるのかをみるようにしましょう！
では実際に`Text`コンポーネントを使ってアプリ名を表示しましょう(私達の作ったコンポーネントとはお別れです)

```tsx:App.tsx
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`に設定しました

https://v2.chakra-ui.com/docs/components/text

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/84380174-3e8a-80a2-1380-cdd980b0e603.png)

タイトルをつけることができたので、ここから支出を入力できるようなコンポーネントを作成していきます！

# 5. 入力フォームの作成

まずはじめに支出の記録をするための入力フォームと追加ボタンを作成していきます。

```src/App.tsx
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/63edab7d-279d-09e2-04f0-69e60b1126a8.png)

このアプリでは支出を記録しますが、もし給料などお金が手元に入ったときには入金をチェックすることでプラスで計算できるようんします。

入力フォームは以下のコンポーネントを利用しました

https://v2.chakra-ui.com/docs/components/input/usage

入金のチェックボックスはこちらを利用しています

https://v2.chakra-ui.com/docs/components/checkbox/usage

入金のチェックボックスと追加ボタンを横並びにしたいので`disploy: flex`をする必要があります。
ChakaUIには簡単に`flex`ができるコンポーネントがあるので使いました

https://v2.chakra-ui.com/docs/components/flex/usage

```tsx
<Flex align="center" justifyContent="space-between">
```

`align="center"`で縦に中央揃え、`justifyContent="space-between"`で左右に均等に配置しました
これらのプロパティは`<Flex>`に用意されているものでドキュメントをみると何が使えるのかを確認できます

https://v2.chakra-ui.com/docs/components/flex/props

```tsx
 <Input placeholder='支出を入力' mb="4px" />
 ```

 この`mb="4px"`もマージンを下に4pxあけるプロパティで、こちらも用意されているものを使っています。

 # 6. リストの表示

 では次にテストデータを用意して支出を表示ていきましょう

 ```tsx:src/App.tsx
 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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/1236da78-aab0-061b-f362-0e87892569bb.png)

まずはテストデータを用意しました
支出と入金があるため、どちらかわかるように`isIncome`という項目を用意しました。
`true`なら入金、`false`なら支出を表現しています

```tsx
 let testData = [
   // 支出と入金のデータを作成
   {
     id: 1,
     title: 'お金を払う',
     isIncome: false,
     amount: 1000,
   },
   {
     id: 2,
     title: 'お金をもらう',
     isIncome: true,
     amount: 1000,
   },
 ]
 ```

 入力フォームとリストの表示の間にマージンをあけたかったので、`div`から`Box`にタグを変えました
 意味合いはあまり変わりませんが、BoxはChakraUIが用意しているコンポーネントなので`mb`のようなプロパティが使えるのでCSSを書かないでスタイリングができます

 ```tsx
        <Box mb="8px">
          <Input placeholder='支出を入力' mb="4px" />
          <Flex align="center" justifyContent="space-between">
            <Checkbox w="100px">入金</Checkbox>
            <Button colorScheme="teal">追加</Button>
          </Flex>
        </Box>
```

リストの表示は以下のように書きました

```tsx
       <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`を使っているため展開すると以下のようになります

```tsx
<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なら+、それ以外なら-を表示しました

```tsx
<Text>{testData[1].isIncome ? "+" : "-"}{testData[1].amount}</Text>
```

ここで疑問に思うのは`key`の存在です

```tsx
<div key={data.id}>
```

リストの表示をするときには`key`という項目に一意の値を入れる必要があります。
これはReact がどのアイテムが変更、追加、または削除されたかを識別するのに役立ちます。
もし`key`がない場合、このリストに変更が加わったときにどの要素が変更されたのかがわからず、支出の記録の一部を再描画するのではなく、全体を再描画することに繋がりパフォーマンスが落ちてしまいます
また、変な挙動を引き起こす原因にもなってしまいます。

簡単な例を表示します。
このアプリケーションは`key`を指定しないで作成しました。

![Peek 2024-08-16 12-49.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/81ebe6f4-f8b5-3e5f-b997-f75b5a213812.gif)

<details><summary>使用したコード</summary>

```tsx
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
```

参考
https://qiita.com/parkon_hhs/items/0794a72648be86be0df4

</details>

リストに追加をすると`ul`の中のすべての`li`が更新されていることがわかります。
Keyを指定することで、追加した部分だけを更新することができるようになります。

最初は難しいと感じると思うので`map`を使ったなら`key`を一意なもので指定しておくことを覚えておけばよいでしょう。

# 7. イベントハンドラ

リストが表示できたので次は実際にインプットフォームに入力をして追加できるようんしましょう
ReactにはイベントハンドラというDom要素を処理してくれる便利な機能があります。

例えば、追加ボタンをクリックしたらコンソールにHello Worldを出すようにしてみましょう

```tsx
<Button colorScheme="teal" onClick={ () => console.log("Hello World")}>追加</Button>
```

`onClick`を利用することでイベントを設定した要素をクリックしたときに関数を実行することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/ef41c285-f2d3-da56-49bd-eb8972ebabec.png)

インプットフォームなど入力する要素に対しては`onChange`が利用できます

```tsx
<Input placeholder='支出を入力' mb="4px" onChange={(e) => console.log(e.target.value)} />
```

onChangeでは`(e) => `と書くことでイベントを受け取ることができます。
イベントの中から変更した値を受け取るには`e.target.value`とすることでインプットフォームの入力内容を表示することが可能です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/95580e34-e6c2-f5c4-eda3-e0e3d97a3e8e.png)

1文字打つたびに変更されるので`onChnage`が動いて値が表示されます。

ではこれらを用いて追加ボタンを押したら記録データのオブジェクトをコンソールに表示するようにしましょう
タイトルの入力フォームもついでに用意します

```tsx:src/App.tsx
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/d581e5c1-2cf7-121d-4e5e-6181ad19110e.png)

入力して追加ボタンを押すことでコンソールに表示ができました。

では、これをテストデータに追加するようにしてみましょう

```tsx
 <Button colorScheme="teal" onClick={ () => testData.push({ id: 999, "title": title, "isIncome": isIncome, "amount": amount})}>追加</Button>
 ```

しかし、追加ボタンを押しても新しい支出は表示されません

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/6a935efb-7106-5220-f8df-56a11e3ab16e.png)

DOM要素を変更して再描画したい場合には、ステートを利用する必要があります。
次はこちらを説明していきます。

# 8. ステート

画面に表示されているデータなどを変更する場合には`useState`というフック(React の機能を接続するための関数)を使います。

実際に使うほうがイメージが付きやすいのでまずは利用したコードから載せます

```tsx:src/App.tsx
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
```

これで実際に追加ができるようになりました

![Peek 2024-08-16 20-27.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/596bcf3e-5524-c733-7684-3f0fedc25ce6.gif)

では詳しく説明していきます。
まずは`useState`の部分です

```tsx
  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がステートの値を更新する関数になります。ステートが更新された場合もし画面に表示されているのであれば描画が行われます。
タイトルや金額もステートを使って管理するようにしました

例えばタイトルが更新されたら以下のようにステートを更新します

```tsx
<Input placeholder='タイトルを入力' mb="4px" onChange={(e) => setTitle(e.target.value)} />
```

チェックボックスはtrue/falseなので、デフォルト値を`false`にして、チェックされたら逆を現在の`isIncome`の逆の値で更新するようにしています

```tsx
const [isIncome, setIsIncome] = useState<boolean>(false) // 初期値 false

<Checkbox w="100px" onChange={() => setIsIncome(!isIncome)}>入金</Checkbox>
```

次に`records`の更新ですが、

```tsx
<Button colorScheme="teal" onClick={() => setRecords([...records, {id: records.length + 1, "title": title, "isIncome": isIncome, "amount": amount}])}>追加</Button>
```

すでにある`records`を展開してその後ろに新しいrecordを追加して新しい配列を作成しています。
ここで疑問に思った人もいるかと思います。

```tsx
<Button colorScheme="teal" onClick={() => setRecords.push({id: records.length + 1, "title": title, "isIncome": isIncome, "amount": amount})}>追加</Button>
```

このようにもとの配列に対して最後に`push`すればよいように思えます。
しかしこれではステートの更新が行われません。ステートの更新には新しい変数を与える必要があります。
もし`push`にしてしまうと配列の中身まではuseStateでは気にしないので(変数のアドレスは同じ)、配列自体が更新されてないと勘違いされてしまうのです。

配列の更新をする場合は展開をするということを覚えておきましょう

最後にいちいち追加するたびにタイトルと金額を消さないといけないのが大変なので直していきましょう

```tsx:src/App.tsx
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`を設定することで現在のステートをインプットの入力として入れてくれます

```tsx
          <Input
            placeholder="金額を入力"
            mb="4px"
            type="number"
            onChange={(e) => setAmount(Number(e.target.value))}
            value={amount}
          />
```

そして追加ボタンを押したら`title``amount``isIncome`を初期値に更新することでフォームから消せます

```tsx
            <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>
```

追加ボタンのところが大きくなってきたので、追加の処理を関数に切り分けてあげましょう

```tsx:src/App.tsx
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という関数に処理を移動しました

```tsx
  const addRecord = () => {
    const newRecord: Record = {
      id: records.length + 1,
      title: title,
      amount: amount || 0,
      isIncome: isIncome,
    };
    setRecords([...records, newRecord]);
    setTitle("");
    setAmount(0);
    setIsIncome(false);
  };
```

そしてハンドラでは関数を呼び出すだけになってとても読みやすくなりました

```tsx
<Button colorScheme="blue" onClick={addRecord}>
    追加
</Button>
```

# 9. useEffect

最後にReactで必ず利用する`useEffect`を紹介します。
`useEffect`とはコンポーネントを外部システムと同期させるための React フックです。

https://ja.react.dev/reference/react/useEffect

画面を表示する前に外部のAPIなどを叩いて記録データを取得して、描画前に用意することで、画面が表示されたときには外部のDBにある支出データを事前に表示することができるようになります。

ここでは簡易的なモックサーバーをjson-serverを使って作成していきます。

```
$ npm i json-server
$ touch db.json
```

db.jsonに今回サーバーから返すjsonを定義します

```json:db.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`を実行することができます

```json:package.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](http://localhost:3000/records)にcurlするとjsonが返ってきました

このデータをアプリを開いたら取得して、`records`の初期値にしてきます。

```tsx:src/App.tsx
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/02f86b65-9c25-0bc4-38d8-409cf0dc4b98.png)

アプリを開くとサーバーから取得した値を初期値にしています。
では`useEffect`の実装を見てみましょう


```tsx
  useEffect(() => {
    getRecords()

    async function getRecords() {
      const response = await fetch('http://localhost:3000/records')
      const data = await response.json()
      setRecords(data)
    }
  }, [])
```

useEffectの中ではasync直接呼び出せないので、非同期でデータを取得する関数を定義して関数を呼び出すようにしています。
データを取得したら`records`の初期値として`setRecords`を使ってデータを入れています。

```tsx
useEffect(() => {
 ここに画面描画前にやりたい処理
 }, []
 ```

 この書き方を覚えておけば初心者の方は大丈夫です
 `[]`も意味があるので、そこは気になる方は調べてみてください

 これにて家計簿アプリは完成です🎉🎉


 # おわりに

 今回はReactをやる中で絶対に抑えておきたい頻出のスキルを一通りハンズオンを通して学習しました
 この内容があればTODOアプリなどを作れるスキルが身についているはずです。
 ぜひともここで習ったことを活かしてアプリを作ってみてください。自分で実際につかうことでより理解を深めることが可能です。

 今回の内容は以下の動画でも学べますのでより詳しく知りたい方はぜひご覧ください

https://youtu.be/_S--bTdPezU?si=g3ehpB1ofW5IE1lU

 ここまで読んでいただけた方はいいねとストックよろしくお願いします。
@Sicut_study をフォローいただけるととてもうれしく思います。

普段はTwitterでエンジニアに関する情報を発信していますのでよければ友達になってください👇

https://x.com/Sicut_study

また明日の記事でお会いしましょう！

# JISOUのメンバー募集中！
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか？

興味のある方は、ぜひホームページからお気軽にご連絡ください！
▼▼▼

https://projisou.jp