目次
はじめに
ギークフィードの松浦です。
この記事はギークフィードアドベントカレンダー2023の5日目の記事になります。
ギークフィードでは勉強会を毎週木曜日に社内勉強会を実施しています。
本記事は私が社内勉強会でReact勉強会を開催した際の資料をブログ公開用に改稿した記事となっています。
React Hooksについて解説した後最後に簡単な演習問題を一問載せてるので是非実装してみてください。
最新のフロントエンド技術の動向について
まずはじめに、Reactは現在、フロントエンド開発において非常に人気があり、広く使用されています。これは、Google TrendsやState of JavaScriptのデータが示す通りであり、Reactに対する需要が非常に高まっていることを示唆しています。
- Google trends
上二つのグラフを見るとReactはフロントエンド技術においてかなり突出して使用されているトレンド技術だということがわかると思います。
そのためReactエンジニアを欲しているWeb系企業はかなり多く、Reactを覚えると市場で市場価値の高いエンジニアになれることは間違い無いでしょう。
Reactの特徴
ReactはMeta社が開発したWebアプリのUI構築に特化したJavaScriptライブラリです。
Meta社のInstagramなどもReactで開発されています。
Reactは以下のような特徴があります。
宣言的なView
React は、アプリケーションの各状態に対応するシンプルな View を設計するだけで、データの変更を検知し、関連するコンポーネントだけを効率的に更新、描画します。
コンポーネント指向
Reactでは、UIを再利用可能な部品(コンポーネント)に分割しそれらを組み合わせてWebアプリを構築します。これにより、コードの再利用性が高まり、保守性が向上します。
仮想DOM
仮想DOMは、Reactが内部的に使用している技術であり、DOMの変更を最小限に抑えることができます。Reactは、仮想DOM上で変更を行い、必要な部分だけを実際のDOMに反映させるため、パフォーマンスが向上します。
React開発環境構築について
ここからはReactでの開発手法を紹介していきます。
プログラミング言語の習得には実際に手を動かし、理解を深めることが重要です。
この記事をご覧になっていただいているみなさんも実際にローカルで動かしてみてください。
前提条件
Node.jsをローカルにインストールしていること
環境情報
私が今回使ってるバージョン(無理に合わせる必要はないです。)
Node.js 20.9.0 (10/30段階でLTS)
環境構築
まず、Nodejsがインストールできたらターミナルで以下のコマンドを打ちます。
今回はTypeScriptを使用します。
TypeScriptを使用しない場合は--template typescript
を入れずにコマンドを打ってください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
$ npx create-react-app sample-app --template typescript Need to install the following packages: create-react-app@5.0.1 Ok to proceed? (y) y npm WARN deprecated tar@2.2.2: This version of tar is no longer supported, and will not receive security updates. Please upgrade asap. Creating a new React app in /Users/matsuuradaiki/sample_app. Installing packages. This might take a couple of minutes. Installing react, react-dom, and react-scripts with cra-template... added 1462 packages in 35s 242 packages are looking for funding run `npm fund` for details Initialized a git repository. Installing template dependencies using npm... added 69 packages, and changed 1 package in 3s 246 packages are looking for funding run `npm fund` for details Removing template package using npm... removed 1 package, and audited 1531 packages in 1s 246 packages are looking for funding run `npm fund` for details 8 vulnerabilities (2 moderate, 6 high) To address all issues (including breaking changes), run: npm audit fix --force Run `npm audit` for details. Created git commit. Success! Created sample_app at /Users/matsuuradaiki/sample_app 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 sample_app npm start Happy hacking! |
これで準備ができました。
上のようなディレクトリが自動生成されていると思います。
またエディターで作ったフォルダを開き以下のコマンドを打ってみましょう。
1 2 |
$ npm start |
するとブラウザが立ち上がり、上のようなページが開かれればOKです。
上のページの描画を定義しているコードは「src/App」にあります。
少し書き換えて反映されるか試してみてください。
関数コンポーネント
ReactにおいてUIを構成する部品のことをコンポーネントと呼びます。
このコンポーネントは関数コンポーネントとクラスコンポーネントの二種類ありますが、現在においては関数コンポーネントを使用することが推奨されています。
昔のReact公式ページはクラスコンポーネントで書かれていましたが、現在は全て関数コンポーネントで書かれています。
古:https://ja.legacy.reactjs.org/
その理由は
- 関数コンポーネントの方がシンプルにコンポーネントを定義できる点
- 後述するReact Hooksの導入によりstateやコンポーネントライフサイクルの実装が容易で柔軟性が高くなったため
となっています。
書き方はシンプルで以下のように定義してあげれば簡単にUIを定義できます。
今回はTypeScriptなのでtitleの型定義も行いました。
1 2 3 4 5 6 7 8 |
import React from "react"; export const MyButton = ({ title }: { title: string }) => { return ( <button>{title}</button> ); } |
このコンポーネントを使うには、呼び出し元のコンポーネントで以下のようにコンポーネントのimportを行い、JSXタグで呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import React from "react"; import logo from "./logo.svg"; import "./App.css"; import { MyButton } from "./components/atoms/MyButton"; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.tsx</code> and save to reload. </p> <a className="App-link" href="<https://reactjs.org>" target="_blank" rel="noopener noreferrer" > Learn React </a> <MyButton title="My Button" /> </header> </div> ); } export default App; |
Learn Reactの下にMyButtonコンポーネントを配置しました。すると、
このように定義したコンポーネントを使うことができます。
なお、Reactの型は他にも
1 2 3 4 5 6 7 8 9 10 11 12 |
import React from "react"; type MyButtonProps = { title: string }; export const MyButton = ({ title } : MyButtonProps) => { return ( <button>{title}</button> ); } |
1 2 3 4 5 6 7 8 9 10 11 12 |
import React from "react"; type MyButtonProps = { title: string }; export const MyButton: React.FC<MyButtonProps> = ({ title }) => { return ( <button>{title}</button> ); } |
などと定義することもできます。
下の場合はReact.FC
を使用して関数コンポーネントの型を定義しています。React.FC
は、Propsの型をジェネリクスとして受け取ります。この方法は、Propsに子要素(children
)が含まれる場合、またはPropsにデフォルト値がある場合に特に役立ちます。
React Hooks
Reactライブラリでの関数コンポーネント内での状態管理やライフサイクルイベントの利用を可能にする新しい機能です。これは、クラスコンポーネントに依存する従来の方法に代わるものとして、React v16.8で導入されました。
React Hooksの主なメリットは以下の通りです:
- 関数コンポーネント内での状態管理: React Hooksは、
useState
フックを介して簡単にコンポーネントの状態を管理するための手段を提供します。これにより、クラスコンポーネントと同様に状態を持つことができます。 - 副作用の管理:
useEffect
フックを使用すると、コンポーネントのレンダリングサイクルの間に発生する副作用(データの取得、サブスクリプションの設定など)を処理できます。 - カスタムフックの作成: 関数内でフックを使用して、カスタムフックを作成できます。これにより、コードの再利用が容易になります。
React Hooksはコンポーネントの記述を簡潔にし、複雑な状態やライフサイクル管理をより直感的かつ効果的に行うことができるようにしてくれます。
useState
useState
は、Reactで状態を保持するためのReact HooksでuseState
を使うことで、コンポーネント内で状態を管理することができます。
Reactではこのstate(状態)が変わったときに再レンダリングされます。
例えば以下のように定義します。
1 2 3 |
const [count, setCount] = useState<number>(0); const [text, setText] = useState<string>(""); |
上記の例では、count
は初期値0の数値型定数で、text
は初期値が空文字列の文字列型定数であり、setCount
はcount
の状態を、setText
はtext
の状態を更新するための関数です。useState
は、配列を返し、配列の1番目の要素には現在の状態の値が、2番目の要素には状態を更新するための関数が格納されます。例えば、以下のようにtext
の値を更新することができます。
1 2 3 |
setCount(1) setText("new text") |
また、setStateにはコールバック関数を入れることができ、その引数は直前の値になります。
1 2 |
setCount(prevValue => prevValue + 1) |
なので上のように定義した場合直前の値に1を足したものがcountにセットされます。
count++; などのようなインクリメントを使い直接countを更新するようなことはできないので注意しましょう。
以下のように定義すると+ボタンを押下するとincrement関数が処理され、count
の値が1増え、ーボタンを押下するとdecrement関数が処理されるのでcount
の値が1減ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import React, { useState } from "react"; import "./App.css"; function App() { const [count, setCount] = useState(0); const increment = () => setCount((prev) => prev + 1); const decrement = () => setCount((prev) => prev - 1); return ( <div className="App"> <h1>Count: {count}</h1> <button onClick={increment}>+</button> <button onClick={decrement}>ー</button> </div> ); } export default App; |
+ボタンを押し続けると無限に増えていきます。
ここで注意していただきたいのが、set
関数を呼び出しても、実行中のコードの状態は変わりません。
以下のコードは、一見するとログにはセットされた新しいカウントが出力されそうですが、そうではありません。
これは、状態がスナップショットのように動作するためです。count
状態を更新すると、新しい状態値を使用して別のレンダリングが要求されますが、すでに実行されているイベント ハンドラーの JavaScript 変数には影響しません。
1 2 3 4 5 6 |
const increment = () => { setCount((prev) => prev + 1); // 下のログではセットする前の値が出力される console.log(count) }; |
処理の中で新しい値を使用したい場合は以下のように新しい値を変数に保存することで回避しましょう。
1 2 3 4 5 6 |
const nextCount = count + 1; setCount(nextCount); console.log(count); // 0 console.log(nextCount); // 1 |
他にもuseRefを使用することでも最新の値を使用することができます。 気になる方は以下を参考に自学してみてください。 https://react.dev/reference/react/useRef
useEffect
useEffect
は、React Hooksの一つで、コンポーネントのライフサイクルに関する処理を実行するために使用されます。例えば、コンポーネントがマウントされた時、アンマウントされた時、または更新された時に実行される処理を定義することができます。
以下は、useEffect
の基本的な使い方です。
1 2 3 4 5 6 7 8 9 10 11 12 |
import { useEffect } from 'react'; export const MyComponent = () => { useEffect(() => { // ここに処理を書く }, []); return ( // コンポーネントのJSX ); } |
第1引数には、実行したい処理を記述します。第2引数には、依存する値の配列を渡します。この配列に含まれる値が変更された場合に、再度処理が実行されます。空の配列を渡すと、初回レンダリングのみ実行されます。
例えば、以下の例ではtext
を依存配列としたuseEffect
でtext
の値が変わる度に長さをtextLength
にセットする処理を行っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import { useEffect, useState } from "react"; import "./App.css"; function App() { const [text, setText] = useState<string>(""); const [textLength, setTextLength] = useState<number>(text.length); useEffect(() => { setTextLength(text.length); }, [text]); // テキストボックスの値が変更されたときに呼び出される関数 const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => { // textの値を更新 setText(e.target.value); }; // テキストボックスと入力された値を表示 return ( <div className="App"> <input className="input" type="text" value={text} onChange={(e) => handleTextChange(e)} /> <div className="text">{`入力したテキスト: ${text}`}</div> <div className="text">{`入力したテキストの文字数: ${textLength}`}</div> </div> ); } export default App; |
上の例くらいの操作であれば
1 2 3 |
const [text, setText] = useState<string>(""); const textLength = text.length |
としても同じなので後者の方が良いです。
また、外部システムと同期するためにuseEffect
を使用することも多いです。
例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import { useState, useEffect } from "react"; type User = { id: number; name: string; }; export const App = () => { const [data, setData] = useState<User[]>(); useEffect(() => { (async () => { const response = await fetch("<https://api.example.com/data>"); const data = await response.json(); setData(data); })(); }, []); return ( <div className="App"> {data ? ( <ul> {data.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ) : ( <p>Loading...</p> )} </div> ); }; |
このようにuseEffectを使うことでAPIからデータを取得し、そのデータを使うことができます。
useMemo useCallback
次に、Reactでは不要な再レンダリングを防ぐために値や関数をキャッシュすることができます。
以下のように書くことで再レンダリングのパフォーマンスを最適化することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import React, { useState, useMemo, useCallback } from "react"; import "./App.css"; function App() { const [text, setText] = useState<string>(""); const textLength = useMemo(() => text.length, [text]); // テキストボックスの値が変更されたときに呼び出される関数 const handleTextChange = useCallback( (e: React.ChangeEvent<HTMLInputElement>) => { // textの値を更新 setText(e.target.value); }, [] ); // テキストボックスと入力された値を表示 return ( <div className="App"> <input className="input" type="text" value={text} onChange={(e) => handleTextChange(e)} /> <div className="text">{`入力したテキスト: ${text}`}</div> <div className="text">{`入力したテキストの文字数: ${textLength}`}</div> </div> ); } export default App; |
useMemo
は第一引数にはキャッシュしたい値を計算する関数を設定します。この関数は純関数で、引数を取らず、任意の型の何らかの値を返す必要があります。第2引数には、依存する値の配列を渡します。この配列に含まれる値が変更された場合に、再度処理が実行されます。例えば空の配列を渡すと、初回レンダリングのみ処理が行われ、その他のレンダリングでは再計算されないためパフォーマンスが最適化されていると言えます。
useCallback
は、関数をメモ化するためのフックです。関数が再生成されることを防ぎ、パフォーマンスを向上させます。
処理に必要なStateは必ず依存配列に入れる必要があります。
今回の場合、依存配列のtextの値が更新された時のみ関数による再計算が行われます。
以下のコードのように依存配列にtextを入れ忘れてしまうと初回レンダリングのみtextLengthの計算処理が行われますのでどれだけテキストボックスに文字を入れてもtextLengthの値は0となります
1 2 |
const [text, setText] = useState<string>(""); const textLength = useMemo(() => text.length, []); |
カスタムフック
カスタムフックは、Reactのフックの一つで、カスタムフックを使用することで、コンポーネントからロジックを抽出し、再利用可能な関数を作成することができます。このようにすることで、UIと処理のコンポーネントを完全に分離することもできるようになります。
UIと処理を分離することで、コンポーネントの記述が簡潔になり、コンポーネントの複雑化を防ぐことができます。また、カスタムフックを使用することで、同じロジックを複数のコンポーネントで再利用することができます。これにより、コードの重複を避けることができ、保守性の高いコードを書くことができます。
以下は、カスタムフックの基本的な使い方です。
カスタムフックは必ずuse
から始める必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { useState, useCallback, useMemo } from "react"; export const useApp = () => { const [text, setText] = useState<string>(""); const textLength = useMemo(() => text.length, [text]); // テキストボックスの値が変更されたときに呼び出される関数 const handleTextChange = useCallback( (e: React.ChangeEvent<HTMLInputElement>) => { // textの値を更新 setText(e.target.value); }, [] ); return { state: { text, textLength }, handler: { handleTextChange }, }; }; |
こちらは先程のコードをロジックのみ切り分けたカスタムフックになります。
これをApp.tsxで読み込むには以下のように書くことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { useApp } from "./hooks/useApp"; import "./App.css"; function App() { const { state: { text, textLength }, handler: { handleTextChange }, } = useApp(); return ( <div className="App"> <input className="input" type="text" value={text} onChange={(e) => handleTextChange(e)} /> <div className="text">{`入力したテキスト: ${text}`}</div> <div className="text">{`入力したテキストの文字数: ${textLength}`}</div> </div> ); } export default App; |
このようにUIコンポーネントでは値や関数を受け取ることができます。
演習
ここまででReactの基礎を解説してきました。
ローカルで手を動かせる人は以下のような動きをするようにコーディングしてみてください。
Ex1)useStateを使い以下の処理を実装してください。
こちらのGithubページに解答例が載っているので参考にしてみてください。
おわりに
以上でReact入門を終わりたいと思います。
以下にこれらのコードが入ったGithubページを載せておきます。
- Twilio Flex v2.x.x系でLINE連携を実装する方法 - 2024-12-23
- AWS Bedrockを活用したAI生成テキスト評価と再生成の実装技法 - 2024-06-17
- AWSから公開されたJavaScriptランタイム「LLRT」を使ったLambdaをAWS CDKで構築する方法 - 2024-02-19
- 【Bun】JavaScriptでシェルスクリプトを書けると噂のBun Shell使ってみた - 2024-02-08
- 【Next.js】Next.js 14をAmplify V6でデプロイ・ホスティングする方法【Amplify】 - 2024-02-06