Reactで評価レート実装

## Reactで評価レートを実装してみよう。
今回は食べログなど評価アプリでよく見る星を作ってみます！

![スクリーンショット 2024-06-05 17.38.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655860/c0e5a8f3-58b7-16c8-0b88-146ab47d0c75.png)

個人でサイトやアプリを作るとき結構使うのではないかなと思ったので参考になれば幸いです。

### バージョン
React 18.3.1
TypeScript 4.9.5
tailwindcss 3.4.3
react-icons 5.2.1
node.js 22.2.0
npm 10.7.0

### 環境構築

まずはお使いのパソコンのターミナルを開いて
```.bash
npx create-react-app {アプリ名} --template typescript
```
TypeScriptを今回は使用していきますがTypeScriptを使用しない場合は
```.bash
npx create-react-app {アプリ名}
```

他にも

Vite 

https://ja.vitejs.dev/guide/

Reactフレームワークを使用する場合

https://ja.react.dev/learn/start-a-new-react-project


など色々あるのでお好きな方法で環境構築してみましょう

```.bash
Success! Created {アプリ名} at /{現在のディレクトリ}/{アプリ名}
Inside that directory, you can run several commands:

  npm start  
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd {アプリ名}
  npm start

Happy hacking!
```

以下のような画面が出てきた場合環境構築は成功です！
cdコマンドを使って命名したアプリ名のフォルダに移動してローカル環境を立ち上げてみましょう。

## 実装

まずはお使いのターミナルもしくはエディタ上のターミナルで以下のようにローカル環境を立ち上げるコマンドを入力して、下のような画面が表示されることを確認しましょう。
```.bash
# npmを使用の場合
npm run start
# yarnを使用の場合
yarn start
```

![スクリーンショット 2024-06-03 11.14.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655860/e7f8e975-b47c-a0b4-2d03-1d9ada7bff6e.png)

### 星マークを表示するファイルの作成と準備

```tsx:App.tsx
import React from 'react';

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

export default App;
```

App.tsxファイルに星マークを表示したいのでdivタグの中を消してしまいましょう！

他にもApp.cssとindex.cssの中身も消しておきましょう！

このApp.tsxファイルに直接星マークを表示しても良いのですが、
**複数人で開発する際どのファイルが星を表示させているのかを一目でわかりやすくしたいので**星を表示させるだけのファイルを作ります。

src配下に**componentsフォルダ**を作成して、さらにそのフォルダ配下に **atomsとorganisms**という名前のフォルダを作成します。

表示したい画面の機能や規模で表示するファイルを分けることで、複数人で開発するときに分かりやすくします。
![スクリーンショット 2024-06-03 11.45.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655860/c04e9dd8-9896-9f56-8bcc-ce7994a91fcf.png)


なぜこのフォルダ名にしたかは下のリンクをご覧ください！
atomicデザイン　→　

https://udemy.benesse.co.jp/design/web-design/atomic-design.html

### TailwindCSSとReactIconsのインストール
① TailwindCSSをReactで読み込む

https://tailwindcss.com/docs/installation/using-postcss

```js:tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  // ドキュメントのコピペを以下のように変更　tsxファイルに適用させる
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

② 星のアイコンを外部パッケージで用いる

https://react-icons.github.io/react-icons/

```.bash
# npmを用いる場合
npm install react-icons --save
# yarnを用いる場合
yarn add react-icons
```

### 星たちの実装(完成コード)
以上が終わったら、

**atomsフォルダ配下にStar.tsx**ファイルを作成し以下を記述
```tsx:Star.tsx
import { FC } from "react"
import { FaStar } from "react-icons/fa"

interface Props {
    selected:boolean
    onSelect:() => void
}  

const Star:FC<Props> = (props) => {
    const {selected,onSelect} = props
    return (
        <FaStar color={selected ? "red" : "grey"} onClick={onSelect}/>
    )
}

export default Star
```
**organismsフォルダ配下にStarRates.tsx**ファイルを作成し以下を記述
```tsx:StarRates.tsx
import { FC } from "react"
import Star from './atoms/Star'
import { useState } from 'react'

const StarRates:FC = () => {
    const totalStars = 5
    const [selectedStars,setSelectedStars] = useState(3)
    return (
        <div className='flex gap-1 mt-40'>
            {[...Array(totalStars)].map((_,i)=>(
                <Star 
                    key={i} 
                    selected = {selectedStars > i} 
                    onSelect={() => setSelected(i+1)}
                />
            ))}
            <p>
                このお店の評価は {selectedStars} / {totalStars} です！！
            </p>
        </div>
    )
}

export default StarRates
```
**最後にApp.tsxファイルでStarRatesファイルを読み込む**
```tsx:App.tsx
import React from 'react';
import  StarRates  from './components/organisms/StarRates'

function App() {
  return (
    <>
        <StarRates/>
    </>
  );
}

export default App;
```

## 解説

### コンポーネントの読み込み
```tsx:App.tsx
import  StarRates  from './components/organisms/StarRates'
```
コンポーネントの読み込みの際は上記のように書きます、今回はStarRatesファイルで
export default StarRates とあるので、中カッコをつける必要はなくパスさえ合っていれば自由に名前を決める事ができます。
```tsx:App.tsx
import  Stars  from './components/organisms/StarRates'

function App() {
  return (
    <>
        <Stars/>
    </>
  );
}

export default App;
```

反対に、export Starrates　とある場合は
```tsx:App.tsx
import  { StarRates }  from './components/organisms/StarRates'
```
と書きます。

https://monotein.com/blog/import-export-react

### 実装手順
```tsx:StarRates.tsx
import { FC } from "react"
import Star from './atoms/Star'
import { useState } from 'react'

const StarRates:FC = () => {
    const totalStars = 5
    const [selectedStars,setSelectedStars] = useState(3)
    return (
        <div className='flex gap-1 mt-40'>
            {[...Array(totalStars)].map((_,i)=>(
                <Star 
                    key={i} 
                    selected = {selectedStars > i} 
                    onSelect={() => setSelected(i+1)}
                />
            ))}
            <p>
                このお店の評価は {selectedStars} / {totalStars} です！！
            </p>
        </div>
    )
}

export default StarRates
```
① まず星を最大5つ表示したいので　totalStars　という定数に5を代入してあげます。
② ユーザー側は点数(数値)で入力することを想定して　
    **useStateの初期値は3でnumber型にします。**
    このステートの値が変わるとreactが差分をチェックして再レンダリングをします。
    そしてDOMに反映されるという仕組みになっています。
    
https://ja.react.dev/learn/state-a-components-memory

③ ステートの初期値を色に反映させる
```tsx
{[...Array(totalStars)]
console.log({[...Array(5)])
```
まず、0をスタートとして中身がundefinedである長さ5である配列を作成します。
これで配列にそれぞれindexを割り振る事ができました。
    
   ![スクリーンショット 2024-06-03 12.56.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655860/c6414531-db12-b07e-a9f2-e2edf7260cab.png)

星を5個作成し、それぞれの星に番号を振っていきます。　
この番号を用いて色を後でつけていきます。
```tsx
{[...Array(totalStars)].map((_,i)=>( 
    <Star key={i}/>
))}
```
上記で作った番号が振られた配列を展開して、Starコンポーネントに　対応する番号を0から順番に振っていきます。
map関数の第一引数は中身がundefinedなので定義しますが使用しません。
第二引数がindexなのでこれをiと定義します。

**keyは配列を展開した際に各々の要素を区別できるように配列の要素ごとに別の値を用意してあげます。**
今回はindexにしましたがあまり良くありません、というのも配列の中身が削除されたり中身が入れ替わったりする際意味をなさなくなる可能性があるからです。

そのような場合はuuidなど必ず他と被らない値を用意してくれる外部パッケージなどを用いることが多いです。

https://www.npmjs.com/package/uuid


このままでは無色の星が5つ表示されているだけなのでstateの点数を画面に反映させます。
```tsx:StarRates.tsx
{[...Array(totalStars)].map((_,i)=>( 
    <Star 
        key={i}　　
        selected={selectedStars > i}
    />
))}
```

```tsx:Star.tsx
interface Props {
    selected:boolean
}  

const Star:FC<Props> = (props) => {
    const { selected } = props
    return (
        <FaStar color={selected ? "red" : "grey"} />
    )
}
```
selectedStarsの値は3なのでiが0、１、2のStarコンポーネントのSelectedがtrueになり、trueの時は星の色が赤色になります。
これで色をつける事ができました。

### クリックして評価を変える
```tsx:StarRates.tsx
{[...Array(totalStars)].map((_,i)=>(
    <Star 
        key={i} 
        selected = {selectedStars > i} 
        onSelect={() => setSelectedStars(i+1)}
    />
))}
```

```tsx:Star.tsx
interface Props {
    selected:boolean
    onSelect:() => void
}  

const Star:FC<Props> = (props) => {
    const {selected,onSelect} = props
    return (
        <FaStar color={selected ? "red" : "grey"} onClick={onSelect}/>
    )
}
```

StarRates(親)からStar(子)にselectedStars(state)を自身のindexより1大きい値にする関数を渡してあげます。

例えば左から2番目の星(評価2にしたい)をクリックした際、クリックされるStarコンポーネントのiは０、１と来ているので１です。　赤色に光る星はselectedStars > i がtrueでなければならないので、この場合はstateの値をi+1してあげればselectedStarsの値は2になりiが0と１のStarコンポーネントの星だけ光ります。
結果として2つ星が光っていることになります。


![スクリーンショット 2024-06-05 17.38.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655860/c0e5a8f3-58b7-16c8-0b88-146ab47d0c75.png)

### 参考文献
https://www.oreilly.co.jp/books/9784873119380/