Reactで評価レートを実装してみよう。
今回は食べログなど評価アプリでよく見る星を作ってみます!
個人でサイトやアプリを作るとき結構使うのではないかなと思ったので参考になれば幸いです。
バージョン
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
環境構築
まずはお使いのパソコンのターミナルを開いて
npx create-react-app {アプリ名} --template typescript
TypeScriptを今回は使用していきますがTypeScriptを使用しない場合は
npx create-react-app {アプリ名}
他にも
Vite
Reactフレームワークを使用する場合
など色々あるのでお好きな方法で環境構築してみましょう
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コマンドを使って命名したアプリ名のフォルダに移動してローカル環境を立ち上げてみましょう。
実装
まずはお使いのターミナルもしくはエディタ上のターミナルで以下のようにローカル環境を立ち上げるコマンドを入力して、下のような画面が表示されることを確認しましょう。
# npmを使用の場合
npm run start
# yarnを使用の場合
yarn start
星マークを表示するファイルの作成と準備
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という名前のフォルダを作成します。
表示したい画面の機能や規模で表示するファイルを分けることで、複数人で開発するときに分かりやすくします。
なぜこのフォルダ名にしたかは下のリンクをご覧ください!
atomicデザイン →
TailwindCSSとReactIconsのインストール
① TailwindCSSをReactで読み込む
/** @type {import('tailwindcss').Config} */
module.exports = {
// ドキュメントのコピペを以下のように変更 tsxファイルに適用させる
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
② 星のアイコンを外部パッケージで用いる
# npmを用いる場合
npm install react-icons --save
# yarnを用いる場合
yarn add react-icons
星たちの実装(完成コード)
以上が終わったら、
atomsフォルダ配下に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ファイルを作成し以下を記述
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ファイルを読み込む
import React from 'react';
import StarRates from './components/organisms/StarRates'
function App() {
return (
<>
<StarRates/>
</>
);
}
export default App;
解説
コンポーネントの読み込み
import StarRates from './components/organisms/StarRates'
コンポーネントの読み込みの際は上記のように書きます、今回はStarRatesファイルで
export default StarRates とあるので、中カッコをつける必要はなくパスさえ合っていれば自由に名前を決める事ができます。
import Stars from './components/organisms/StarRates'
function App() {
return (
<>
<Stars/>
</>
);
}
export default App;
反対に、export Starrates とある場合は
import { StarRates } from './components/organisms/StarRates'
と書きます。
実装手順
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に反映されるという仕組みになっています。
③ ステートの初期値を色に反映させる
{[...Array(totalStars)]
console.log({[...Array(5)])
まず、0をスタートとして中身がundefinedである長さ5である配列を作成します。
これで配列にそれぞれindexを割り振る事ができました。
星を5個作成し、それぞれの星に番号を振っていきます。
この番号を用いて色を後でつけていきます。
{[...Array(totalStars)].map((_,i)=>(
<Star key={i}/>
))}
上記で作った番号が振られた配列を展開して、Starコンポーネントに 対応する番号を0から順番に振っていきます。
map関数の第一引数は中身がundefinedなので定義しますが使用しません。
第二引数がindexなのでこれをiと定義します。
keyは配列を展開した際に各々の要素を区別できるように配列の要素ごとに別の値を用意してあげます。
今回はindexにしましたがあまり良くありません、というのも配列の中身が削除されたり中身が入れ替わったりする際意味をなさなくなる可能性があるからです。
そのような場合はuuidなど必ず他と被らない値を用意してくれる外部パッケージなどを用いることが多いです。
このままでは無色の星が5つ表示されているだけなのでstateの点数を画面に反映させます。
{[...Array(totalStars)].map((_,i)=>(
<Star
key={i}
selected={selectedStars > i}
/>
))}
interface Props {
selected:boolean
}
const Star:FC<Props> = (props) => {
const { selected } = props
return (
<FaStar color={selected ? "red" : "grey"} />
)
}
selectedStarsの値は3なのでiが0、1、2のStarコンポーネントのSelectedがtrueになり、trueの時は星の色が赤色になります。
これで色をつける事ができました。
クリックして評価を変える
{[...Array(totalStars)].map((_,i)=>(
<Star
key={i}
selected = {selectedStars > i}
onSelect={() => setSelectedStars(i+1)}
/>
))}
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は0、1と来ているので1です。 赤色に光る星はselectedStars > i がtrueでなければならないので、この場合はstateの値をi+1してあげればselectedStarsの値は2になりiが0と1のStarコンポーネントの星だけ光ります。
結果として2つ星が光っていることになります。
参考文献