React

これから始めるReact入門。とりあえずこれだけでOK!

更新日:2025/12/25

Reactは難しいと思っていましたが、いくつかの要点をつかんでおけばとても簡単だと感じるツールでした。
そこで今回は、「これだけ知っていればReactを使ってWebアプリのUIを作成開始できる」ということを目標に記事を作成します。

 

Reactとは?導入目的は?

ReactはMeta社が開発した、JavaScriptライブラリです。
ブラウザアプリの画面(UI)構築と画面更新ロジックを単純化するための仕組みを提供してくれます。

ユーザアクションに連動して複雑な画面更新を行う場合、整合性を確認しながら更新ロジックを組み立てる必要があります。

ボタンが押されたら、メッセージを変更したり、他のボタンを有効化したり、タブを切り替えたり...
やることが多いとパニックになりますよね!

ですがReactを使用すると、画面更新を意識する必要がなくなります。
少ないコードでアプリを構築できるため高い保守性を維持することができ、潜在的な不具合を減らすことができます。

想定していない操作でも不具合回避できる!...かもしれない

Reactはアプリの画面操作をサポートするライブラリです。
そのため、記事などの画面操作がないページは、Reactを導入する意味はありません。

またReactは比較的重いモジュールです。
非常に複雑な計算や外部アプリ連携等をしていても、ボタンを押したら結果を表示するだけの計算フォームなら、Reactはオーバースペックです。

プロジェクト全体の規模ではなくて、UIの複雑さで導入するかどうかを判断しよう!

なおReactは初期画面の構築もプログラム内に組み込む必要があります。
HTMLタグで画面構成されている既存ページをReactに移行するのは、とてもコストがかかります。
問題なく動作しているなら、そのままにしておいた方がよいでしょう。

 

Reactの仕組み

Reactプログラミングの主な作業は、疑似的なDOM要素(React要素)を返す関数(コンポーネント)をパーツ単位で作成することです。
※初期のコンポーネントはクラスベースでしたが、現在は関数ベースで記述します

クラスベースのWeb記事は内容古いかもしれないから注意です!

そしてトップレベルのコンポーネントからツリー状に子コンポーネントを構成して、一つの画面を構築します。

各コンポーネントは、親コンポーネントから受け取ったデータとコンポーネントに関連付けられたデータから、React要素を生成します。

親コンポーネントから受け取ったデータは、Reactではpropsと呼んでいるよ!

UIの状態が変更、例えばボタンが押されたら、コンポーネントに関連付けられたデータを変更してReactに通知します。
すると、Reactはコンポーネント関数を呼び出してReact要素を再生成します。

  1. コンポーネントを呼び出してReact要素を生成
  2. ボタンが押されたらデータを変更してReactに通知
  3. コンポーネントを呼び出してReact要素を生成

つまり更新ロジックは関連付けられたデータの変更だけでよいのです。

 

仮想DOM

ブラウザの画面更新(DOM操作)は、比較的重い処理です。
Reactはコンポーネントの再生成を頻繁に行いますが、そのたびにDOM更新していたらアプリ全体が重い印象を受けます。

そこでReactは、更新前と更新後の仮想DOM(DOM構造を単純化したオブジェクト)を比較して、差異がある部分のみDOMに反映します。

仮想DOMはReactの中核的な技術ですが、プログラミング上では意識しなくて大丈夫です。

 

Reactの環境設定

ここから実践です!

ReactはNode.jsで動作します。
次のリンク先からインストーラーを取得して、Node.jsをインストールしましょう。

WindowsとMacは、次のページからインストーラーを取得
Node.jsダウンロード
その他のOSは、次ページを参照
パッケージマネージャを利用した Node.js のインストール

今回は高速フロントエンドビルドツールのViteで開発環境を作成します。

Viteは最近注目され始めた高速フロントエンドビルドツールです

JavaScriptで開発する場合は、次のコマンドをコマンドプロンプト等で実行しましょう。

npm create vite@latest プロジェクト名 -- --template react

プロジェクト名は、他の文字列に置き換えてください。入力した文字列でフォルダが作成されます。

TypeScriptで開発する場合は、次のコマンドを実行しましょう。

npm create vite@latest プロジェクト名 -- --template react-ts

詳しくは、次のページを参照してください。

 

初期コードの解説/ルートの作成/cssのインポート

Reactの開発環境を作成できたら、index.htmlを開きます。

index.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-project</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

このHTMLがベースとなります。
言語はenになっているのが気になりますが、とりあえずこのまま進めます。

bodyタグ内のscriptタグを見てください。

 <script type="module" src="/src/main.jsx"></script>

/src/main.jsxを参照していますね。
srcフォルダ内のmain.jsxを開きましょう。

src/main.jsx

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
)

このコードが、Reactのメインとなるコードです。

createRoot関数はReactのコンポーネントを表示するHTML要素を受け取って、ルートコンポーネントを作成します。
次にルートコンポーネントのrender関数に、React要素を渡します。

React要素はJSX構文で生成される仮想的なDOM要素です

render関数の引数を見てください。
次のReact要素を生成しています。

  <StrictMode>
    <App />
  </StrictMode>

<StrictMode>は、開発時専用の挙動と警告を有効にします。本番には影響しません。
バグの検証のために、一回の更新でコンポーネント関数を2回呼ぶなどの本番環境とは異なる挙動があります。

コンポーネントは純粋な関数であるべき、とされています。
純粋な関数は同じ条件で呼ばれたとき同じ値を返します。
そこで2回呼ぶことで、同じ値かどうかを確認しています。

<App>は、/src/App.jsxから読み込んだコンポーネントです。
これについては後述します。

次に3行目に注目です。

import './index.css'

cssファイルをインポートすると、出力したHTMLファイルに組み込んでくれます。
※ReactではなくViteの機能です。

Viteの開発モード時(npm run dev)はインラインスタイルで組み込みます。
ビルド後(npm run build)は、distフォルダに出力されたHTMLファイルにlinkタグで組み込まれます。

ビルド後のindex.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>react-test</title>
    <script type="module" crossorigin src="/assets/index-DXF4Zhuh.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/index-DQ3P1g1z.css">
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

scriptタグで /assets/index-DXF4Zhuh.js を読み込んでいます。
このファイルは /src/main.jsx をjsに変換(トランスパイル)後に依存モジュールのバンドルや最適化をおこなったものです。
※複数のファイルに分割されることがあります。

 

Reactコードの記述準備

/src/main.jsx で /src/App.jsx を読み込んでいます。

import App from './App.jsx'

今後の開発作業は、/src/App.jsxでおこないます。

/src/App.jsxを開きましょう。

いろいろ記述されていますが、次の内容だけ残して削除してください。

  // Appコンポーネント
function App() {

}

export default App

 

開発モードで実行

次のコードを実行して、viteの開発モードサーバーを起動しましょう。

npm run dev

次のように表示されたら、ブラウザでURLにアクセスします。

➜  Local:   http://localhost:5174/

現時点では何も表示されませんが、次項からのコードを張り付けて保存しましょう。
すると、自動でブラウザの内容が変更されます。

 

JSXでの記述方法

ReactはHTMLタグに似たJSXという構文をプログラムコード内で使用できます。
ここではJSXについて解説します。

JSXはブラウザ上で解釈できません。そのため、JavaScriptに変換する作業(ビルド)が必要です。
※ビルドに必要な設定は、Viteでの環境設定構築時に組み込まれています。

React要素の生成

JSXはReact要素(オブジェクト)を生成します。
{ }を使用して、React要素や関数の戻り値をJSX内に埋め込むことができます。

function App() {
  const title = <p>タイトル</p>
  const paragraph = (text)=><p>{text}</p>

  const paragraphs = <div>
    {title}
    {paragraph("文字列1")}
    {paragraph("文字列2")}
  </div>

  return paragraphs;
}

実行結果:

タイトル

文字列1

文字列2

閉じタグが無いタグ

img等の閉じタグが無いタグは、末尾に "/" を記述します。
記述がないとエラーになります。

function App() {
  return <p>段落1<br/>段落2<br/>段落3</p>;
}

実行結果:

段落1
段落2
段落3

補足:returnで複数行のJSX構文を記述するときは ( ) で囲う

JavaScriptの仕様上、return行に有効なコードが何も記述されていないとundefinedを返します。
体裁を整えたりコメントを挿入したりなどで、undefinedを返してしまうミスは意外と多いです。

NGコード

function App() {
  return  // 段落を返す
    <p>段落1<br/>
    段落2<br/>
    段落3</p>
}

()で囲うことでミスを防ぐことができます。

OKコード

function App() {
  return ( // 段落を返す
    <p>段落1<br/>
    段落2<br/>
    段落3</p>
  );
}

他の言語に慣れていると、やってしまうかもしれません

タグ名の開始文字について:重要

JSXは、タグ名が大文字で開始されているかどうかで異なる解釈をおこないます。

大文字で開始 → コンポーネント
小文字で開始 → HTMLタグ

大文字で開始した場合は、コンポーネントが指定されたと判断されます。
そのため、コンポーネントが定義されていない場合はエラーになります。

小文字で開始した場合は、HTMLタグが指定されたと判断されます。
コンポーネントが定義されていたとしても、そのままHTMLタグとして出力されます。

function App() {
  return (<>
    <p>HTMタグ</p>
    <P/>
  </>);
}
function P(){
  return <p>コンポーネント</p>
}

実行結果:

HTMタグ

コンポーネント

この法則を知らなくて、既存HTMLタグをコンポーネントにできるかも!?
と思ったけどできなかった...

class属性の設定方法

JSXはHTMLタグに似た形式でclassやstyle等の属性を設定できます。

JavaScriptはclassが予約語で使用できないため、
classNameを使用します。
複数のクラスを指定する場合はスペースで区切ります。

function App() {

  return (
  <div className="red bold">
    <p>クラス指定</p>
  </div>
  );
}

クラス名を変数で指定するときは、{ }で囲みます。

function App() {
  const className = "red bold";

  return (
    <div className={className}>
      <p>クラス指定</p>
    </div>
  );
}

クラスの実体は、/src/index.cssに追加しましょう。

.red{
  color: red;
}
.bold{
  font-weight: bold;
}

実行結果:

文字列1

style属性の設定方法

style属性は、属性名をキーとしたオブジェクトで指定します。

function App() {

  const style = {
    color:"blue",
    fontWeight:"bold"
  };

  return (
  <div style={ style } >
    <p>スタイル指定</p>
  </div>
  );
}

※-で区切られたcssプロパティは、キャメルケースで記述します。
キャメルケースは、-で接続する文字列を大文字で開始する表記方法です。
(広義のキャメルケースは数種類のパターンがあります)

実行結果:

文字列1

変数を使わずにstyle属性を指定する場合次のようになります。

<div style={ {
    color:"blue",
    fontWeight:"bold"
  } } >

{{ }}と二重になっていて少し違和感がありますが、{ }の中にオブジェクトを記述しているだけですね。

その他の属性の設定方法

idやsrc、data属性等は、HTMLでの表記と同じ形式で記述します。

function App() {
  return (
  <div id="id1" data-id="id1" data-name="div1">
    <p>その他の属性の設定方法</p>
  </div>
  );
}

-を含んだ属性も、そのまま記述します。
style属性のようにキャメルケースで記述しないことに注意しましょう。

イベントハンドラ

要素クリック等のイベントは、HTMLのようにonから始まるイベント名を指定します。
ただし、on以降のイベント名は大文字から始めます。

HTML → onclick
React → onClick
DOMReact
clickonClick
mousedownonMouseDown
mousemoveonMouseMove
keydownonKeyDown
input / changeonChange(実質input)
submitonSubmit
focusonFocus(バブリングあり)
bluronBlur(バブリングあり)
function App() {
  const clickHandler = (e) => {
    alert(`${e.target.id}がクリックされました!`);
  };
  return <button id="button1" onClick={clickHandler}>クリック!</button>
}

実行結果:

Reactのイベントハンドラは、ブラウザの差異を考慮した処理等の独自処理を行っています。
そのため、DOMのリスナーイベントで受け取る引数の内容と異なる内容を受け取る可能性があります。

onChangeイベントとonInputイベントは、DOMのinputイベントと同じ動作です。
そのためユーザーが入力するたびに呼び出されます。

onFocusとonBlurはバブリングします。
DOMのfocusとblurイベントはバブリングしません。

onChangeをDOMのchangeイベントと同じだと思っていると、地獄を見るよ!

イベントはバブリングフェーズで呼び出されます。
キャプチャフェーズで呼び出す場合は、末尾にCaptureを記述します。

バブリングフェーズ → onclick
キャプチャフェーズ → onclickCapture

イベント監視はルートコンポーネントでおこないます。
各要素にリスナーを設定していないため、比較的高速に動作します。

 

ファイルの拡張子

JavaScriptファイルの拡張子は .js ですが、コードにJSX構文が含まれいる場合は .jsxを使用します。
そのためプロジェクト内に二つの拡張子が存在することになります。

JSXを含まない → .js
JSXを含む → .jsx

JSXを含まない(TypeScript) → .ts
JSXを含む(TypeScript) → .tsx

次のようにReactをインポートしていても、JSXを使用していなければ拡張子は .js です。

import { useState } from "react";

JSXを含まないファイルを .jsx にしても問題なく動作します。
ですが、使い分けることで、

.jsxだからUI操作だな!

と判断できます。

 

コンポーネント

コンポーネントはReact要素を返す関数です。
JSXのタグ名として使用できます。
※関数名は大文字で始める必要があります。

コンポーネント例

  // Appコンポーネント
function App() {
  const clickHandler = (e) => {
    alert(`${e.target.id}がクリックされました!`);
  };

  return <MyButton id="id1" clickHandler={clickHandler} title="click!" />;
}
  // MyButtonコンポーネント
function MyButton(props) {
  return <button id={props.id} onClick={props.clickHandler}>{props.title}</button>
}
export default App

これまで出てきたApp関数もコンポーネントです。
上記のコードはAppコンポーネント内でMyButtonを子コンポーネントとして呼び出しています。

コンポーネント引数

コンポーネント例のMyButtonコンポーネント呼び出し時に指定された属性は、オブジェクトにまとめられてMyButton関数に渡されます。
MyButton関数の引数propsは、次のような内容になっています。

JSX

<MyButton id="id1" clickHandler={clickHandler} title="click!" />

 ↓ ↓ ↓

コンポーネントで受け取るオブジェクト

{
  id: "id1",
  clickHandler: function(){},
  title: "click!"
}

Reactコンポーネントの引数は、次のように引数内でRestパラメータを使って展開することが推奨されているようです。

  // MyButtonコンポーネント
function MyButton( { id, clickHandler, title }) {
  return <button id={id} onClick={clickHandler}>{title}</button>
}

ただし、全ての引数をそのまま子コンポーネントに渡すケースなどは展開する必要がありません。
ケースバイケースで使い分けましょう。

function MyApp(props){
  return <OthetApp {...props} />; // propsを展開して渡す
}

複数のコンポーネントを返す

コンポーネントは親コンポーネントを一つまたはコンポーネント配列を返す必要があります。

Fragmentでまとめる

Fragmentを使用することで、仮想的な親コンポーネントを設定できます。
Fragmentは、<>...</>で記述します。

function MyApp(){
  return (<>
    <MyButton id="id1" clickHandler={clickHandler} title="click!" />
    <MyButton id="id2" clickHandler={clickHandler} title="click!!" />
    <MyButton id="id3" clickHandler={clickHandler} title="click!!!" />
    </>)
}

Fragmentの代わりに<div>...</div>を記述することもできますが、DOM上にdivタグを追加されます。
親タグを追加する必要がないなら、Fragmentを利用しましょう。

配列で返す

コンポーネントを配列で返すこともできます。
ただし、各コンポーネントに値が異なるkey属性をセットする必要があります。

  return [
    <div key="key1" ></div>,
    <div key="key2" ></div>,
    <div key="key3" ></div>,
  ]

DOMを生成させない場合

コンポーネントでDOMを生成させる必要がない場合、nullまたはfalseを返します。

function MyApp({show}){
  if( !show ) return null;
  return <p>表示されました</p>
}

親側で子コンポーネントを呼び出さないというアプローチも可能です。

親側で子コンポーネントを呼び出さない

function App() {
  // 変数showに関するコード
   ・・・・・

  return (
    {!show && MyApp() }
  );
}

しかし子コンポーネントを呼び出さないと、その子コンポーネントが破棄される可能性があります。
マウントとアンマウント参照)
そのため子コンポーネントに関連付けられたuseStateやuseRef等のReactフック(後述)を使用している場合、関連する情報が失われます。

子コンポーネントを呼び出しnullまたはfalseを返すことで、これらの情報を保持できます。

 

Reactフック

Reactフックはコンポーネントの状態を管理する仕組みであり、useから始まる関数です。

ReactのコンポーネントはReact要素を返す関数のため、インスタンスを生成しません。
そのため、独自データを保持できません。

そこで、コンポーネントが独自データを扱えるようにした仕組みがReactフックです。
※実際にはコンポーネントからReactの様々な機能を呼び出す仕組みです。

Reactは約20のフックが用意されています。
今回は、代表的なものを紹介します。

フック用途
useStatestate変数の取得・変更と再レンダーのトリガー関数を取得
useRef1) JSXに関与しない変数の取得・変更
2) 実DOMへの参照を取得
useMemoコストが高い(時間がかかる)計算結果をキャッシュ
useCallback関数をキャッシュ
useEffect実DOM反映後に関数を呼び出す
useContextグローバル変数のようなものをコンポーネントで共有する

フックの紹介の前に、フックを利用する上での補足事項を解説します。

レンダリングと再レンダリングとフック

コンポーネントの初回呼び出し(レンダリング)時は、Reactフック関数に渡された引数を初期値としてセット後に返します。
2回目以降の呼び出し(再レンダリング)では引数が無視され、現在の値を返します。

import { useState } from "react";

// Appコンポーネント
function App() {
  const [count, setCount] = useState(0); // 1 , 4

  const clickHandler = () => {
    setCount(count+1); // 2
  };
  return <button id="button1" onClick={clickHandler}>クリック!({count})</button>
}

export default App
1.初回呼び出しで現在値に0をセットし、現在値を取得。
2.ボタンが押されたら 1を加算して現在値を更新。
3.更新と連動してReactがコンポーネントを再度呼び出す。
4.現在値を取得

毎回同じフックを同じ順番で呼び出す:重要

コンポーネント内でReactフックを複数回呼び出すことができます。
Reactは返す値を、呼び出された順番で管理しています。

そのため、レンダー毎に呼び出し順番が変化しないようにコードを作成しましょう。

NGコード

const [count, setCount] = useState(0);
if( flg ){
    const [count2, setCount2] = useState(0);
}
const [count3, setCount3] = useState(0);

flgがtrueからfalseに変化した場合、count2の値をcount3が取得します。

使用するかどうかに関わらず、フックは必ず同じ順番で呼び出すようにしましょう。

OKコード

const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, setCount3] = useState(0);
if( flg ){
  ...
}

useStateを3回呼んでるけど、オブジェクトにして1回でよくない?

useStateでオブジェクトを使用することは推奨されません。

useStateは現在値をそのまま返すので、取得したオブジェクトのプロパティ変更後にセット関数に渡しても、変更されたと判断されません。

state.a = 2; // stateとsetStateが管理している現在値は同じもの
setState(state); // 同じものを比較するので、更新されていないと判断

そのためオブジェクトの複製する必要がありますが、複製したオブジェクトはプロパティが同じでも変更されたと判断されます。

setState( { ...state } ); // プロパティを変更していなくても、更新されたと判断

勘違いしやすい現象のため不具合の原因になります。

useStateフック

useState関数はコンポーネントの状態(state変数)を管理するReactフックです。
関数を呼び出すと、現在のstate変数の値と、state変数の更新関数を返します。

現在のstate変数値と異なる値で更新関数を呼び出すと、コンポーネントが再レンダーされます。

構文

const [state, setState] = useState(initialState)
  1. initialState:
    初期値 または 関数
    関数が指定された場合、初回呼び出し時にReact内部で実行されて戻り値が初期値として利用されます。
state:現在値
setState:引数として値または関数を受け取る、セット(更新)関数

関数は現在値を引数で受け取り新しい値を返す。
返された値は、現在値にセットされる。

useState関数にセットするのは、React要素生成で必要な値です。
React要素生成に必要ない値は、useRefを使用しましょう。

コード例1:入力された文字を大文字に変換

import { useState } from "react";

// Appコンポーネント
function App() {
  const [value, setValue] = useState("");

  const handleChange = (e) => {
    setValue(e.target.value.toUpperCase());
  };

  return (
    <input value={value} onChange={handleChange} />
  );
}

export default App

毎回再レンダリングして遅くならない?

変更されるのはvalueの変更だけです。
他の属性が変更された場合も、最小限の更新のみおこないます。
実DOMを直接変更するよりは遅いですが、体感的に遅くなるレベルではありません。

コード例2:フラグでJSXの内容を変える

JSXの内容に含まれていなくても、JSX生成に関与しているならuseStateを使用します。

function App() {
 const [flg, setFlg] = useState(true);
  const text = flg ? "True" : "False";
  return (<>
    <p>{ text }</p>
    <button onClick={() => setFlg(!flg)}>Click!!</button>
  </>);
}

コード例3:子コンポーネントにセット関数を渡す

次のコードは親コンポーネントでカウントを保持して、複数の子コンポーネントに渡しています。
またカウントの更新関数をボタンに渡して、ボタン側でカウント更新しています。

import { useState } from "react";

// Appコンポーネント
function App() {
  const [count, setCount] = useState(0);

  return (<>
    <P1 count={count} />
    <Button1 count={count} setCount={setCount} />
  </>);
}
function Button1({ count, setCount }) {
  const clickHandler = () => { setCount(count + 1); };
  return <button onClick={clickHandler}>Click!!({count})</button>
}
function P1({ count }) {
  return <p>{count}回目</p>
}
export default App

コード例4:セット関数に関数を渡す

次のコードは、セット関数に関数を渡しています。

// Appコンポーネント
function App() {
  const [count,setCount] = useState(1);
   // 増加用関数
  const countUp = () => setCount( c => c + 1 );

  return (<>
    <p>{count}</p>
    <button onClick={()=>{
      countUp();
    }}>1 UP</button>
    <button onClick={()=>{
      countUp();
      countUp();
    }}>2 UP</button>
  </>);
};

カウントを1だけ増加する関数を用意して、増加させたい値だけ増加用関数を呼び出しています。
増加用関数ではsetCountに関数を指定しているため、複数回呼び出すと現在値の更新が連鎖します。

1回目:現在値 + 1 -(更新)→ 現在値
2回目:現在値 + 1 -(更新)→ 現在値

useRefフック

useRef関数は、二つの機能を持つReactフックです。

一つはJSX生成に関与しないデータをコンポーネントに関連付ける機能です。
もう一つは、実DOMへの参照を取得する機能です。

構文

const ref = useRef(initialState)
  1. initialState:
    初期値
ref :currentプロパティに現在値 または 実DOMへの参照をセットしたオブジェクト

この関数にセットするのは、React要素生成で必要な値です。
React要素生成に必要ない値は、useRefを使用しましょう。

JSX生成に関与しないデータをコンポーネントに関連付ける

useRef()を呼び出して取得したオブジェクトのcurrentプロパティにを変更することで、現在の値を更新します。
※useRef()は現在の値を更新しても再レンダーされません。

function App() {
  const count = useRef(0);
  // count.current++; ← NG!!

  const clickHandler = () => {
    count.current++;
    console.log(`${count.current}回目`);
  };
  return <button onClick={clickHandler}>クリック!</button>
}

注意:currentプロパティの値変更は、コンポーネント関数実行中(再レンダー中)におこなえません。
実際には変更できますが、コンポーネントは純粋関数であることが前提となっているため非推奨です。
純粋関数とは毎回同じ結果を返す、かつ、副作用をもたない関数です。

useRef()の値変更は、副作用ですよ

インスタンスをコンポーネントに関連付ける

コンポーネント呼び出しのたびに、useRefの初期値を作成するケースがあります。
例えば次のコードは、呼び出しのたびにCounterクラスのインスタンスを生成して、useRefに渡しています。

class Counter{
  #current;
  constructor(){
    this.#current = 0;
  }
  click(){
    this.#current++;
    console.log(`${this.#current}回目`);
  }
}
// Appコンポーネント
function App() {
  const count = useRef(new Counter()) // 毎回Counterインスタンスを生成

useRefは、呼び出し2回目以降は引数を無視しますが効率が悪いですね。

次のように if( count.current === null ){ } 内での更新は許容されているので、このパターンを使って初期化すると効率がよくなります。

// Appコンポーネント
function App() {
  const count = useRef(null);
  if( count.current === null ){ // 初期値===nullなので、初回のみ真判定
                                // この判定内では更新可能
    count.current = new Counter();
  }
  const clickHandler = () => {
    count.current.click();
  };
  return <button onClick={clickHandler}>クリック!</button>
}

実DOMへの参照を取得する

ref属性が記述されたhtmlタグ(小文字開始のタグ)とuseRef()を組み合わせると、実DOMへの参照を取得できます。

次のコードは、コンポーネントがReact要素を返して実DOMが生成された後、変数pTagにpタグへの参照がセットされます。

function App() {
  const [text,setText] = useState("A");
  const pTag = useRef(null);

  const clickHandler = () => {
    const clientWidth = pTag.current.clientWidth;
    console.log( `現在の幅 ${clientWidth}` );
    setText( text + "A");
  };
  return (<>
    <p ref={pTag}>{text}</p>
    <button onClick={clickHandler}>クリック!</button>
  </>)
}

実DOM参照useRefの悪いコード例

次のコードは実DOMの内容を直接変更しています。
これは避けた方がよいコードです。

NGコード

// Appコンポーネント
function App() {
  const pTag = useRef(null);

  const clickHandler = () => {
    pTag.current.innerHTML += "A"; // NG:実DOMの内容を直接変更
  };
  return (<>
    <p ref={pTag}>A</p>
    <button onClick={clickHandler}>クリック!</button>
  </>)
}

コンポーネントの再レンダリングはReactの状況判断で呼び出されるため、プログラムコードが関与しないタイミングで実行されることがあります。
そのため上記のコードは不特定のタイミングで、pタグの内容が初期化されます。

一見問題なく動作するから、不具合に気が付きにくいコードですね

実DOMの内容を変更しないで、JSXを返しましょう。

OKコード

// Appコンポーネント
function App() {
  const [pTag,setPTag] = useState("A");

  const clickHandler = () => {
    setPTag(pTag + "A");
  };
  return (<>
    <p>{pTag}</p>
    <button onClick={clickHandler}>クリック!</button>
  </>)
}
フォーカス制御やスクロール位置変更など、JSX構文でサポートできない変更なら、実DOMの内容を直接変更してもOK

コンポーネントの実DOMを取得する

実DOMへの参照はref属性が記述されたhtmlタグ(小文字開始のタグ)とuseRef()を組み合わせで取得するため、コンポーネント(大文字開始のタグ)では取得できません。

ですが、コンポーネントにref属性を渡し、受け取ったコンポーネント側でhtmlタグにref属性をセットすると、親側でコンポーネントの実DOMへの参照を取得できます。

// Appコンポーネント
function App() {
  const [text,setText] = useState("A");
  const pTag = useRef(null);

  const clickHandler = () => {
    const clientWidth = pTag.current.clientWidth;
    console.log( `現在の幅 ${clientWidth}` );
    setText( text + "A");
  };
   // refをコンポーネントへ渡す
  return <MyComponent ref={pTag} onClick={clickHandler} text={text} />
}
// MyComponentコンポーネント
function MyComponent({ ref , onClick , text }){
   // 親から受け取ったrefをpタグにセット
  return (<>
    <p ref={ref}>{text}</p>
    <button onClick={onClick}>クリック!</button>
  </>)
}

一部の要素の実DOMでなくて、コンポーネント全体の実DOMは取得可能?

Fragmentや配列で返すケースでは取得できませんが、div等でラップすることでコンポーネント全体の実DOMを取得できます。

function MyComponent({ ref , onClick , text }){
  return (<div ref={ref}>
    <p >{text}</p>
    <button onClick={onClick}>クリック!</button>
  </div>)
}

useMemoフック

useMemo関数は、コストが高い(時間がかかる)計算結果をキャッシュすることを目的とした Reactフックです。
useMemo関数は引数として関数と依存値を受け取り、引数関数の実行結果を返します。

useRefは取得した値を任意の値で変更できましたが、useMemoは変更できません。
useMemoは、前回と異なる依存値を与えたときに、引数として与えた関数を実行してその結果をキャッシュして返すだけです。

また2回目以降に引数として与えた関数を変更した場合、その関数を使用します。

構文

const cachedValue = useMemo(calculateValue, dependencies)
  1. calculateValue:
    何らかの値を返す関数
  2. dependencies:
    コンポーネント関数で使用している変数で、calculateValueで使用するものを配列で指定。
cachedValue :
前回のdependenciesと同じdependenciesが指定されたら、キャッシュの値。
異なるならcalculateValueを実行した結果

コード例1:ボタンが複数回押されたら表示を変更する

次のコードは、ボタンが3回押されたとき文字の繰り返し回数を増加しています。

import { useRef, useState, useMemo } from "react";

// Appコンポーネント
function App() {
  const [textCount, setTextCount] = useState(1);
  const clickCount = useRef(0);
  const text = useMemo(() => "A".repeat(textCount), [textCount]);

  const clickHandler = () => {
    if (clickCount.current < 2) {
      clickCount.current++;
      return;
    }
    clickCount.current = 0;
    setTextCount(textCount + 1);
  }
  return (<>
    <p>{text}</p>
    <button onClick={clickHandler}>Click!!</button>
  </>);
};

useMemoは計算コストが高いとき使用します。
文字列のrepeat関数は、繰り返し回数にもよりますが、今回の使い方では高コストではないでしょう。
また、上記のコンポーネントは基本的にはtextCountが更新されるタイミングで実行されます。
そのためuseMemoのキャッシュ再計算が毎回実行されます。

あまり良い例ではありませんね。

基本的には、
「まずはuseMemoを使わないでコードを作成して、重かったらuseMemo化する」
が推奨です

コード例2:参照の安定化

コンポーネント内で生成したオブジェクトはプロパティ値が同じでも、前回生成したオブジェクトは異なるデータです。

そのためuseMemoの依存値としてオブジェクトを使用した場合、異なる値が指定されたと判断され、関数が実行されます。

function App({value1,value2}) {
  const valueObj =  { value1,value2 };
     // 毎回、時間がかかる関数()を実行
  const result = useMemo(() => 時間がかかる関数(valueObj), [valueObj]);

そこで、useMemo()を使ってオブジェクトをキャッシュします。

function App({value1,value2}) {
  const valueObj = useMemo( ()=>( { value1,value2 } ) , [value1,value2] );

  const result = useMemo(() => 時間がかかる関数(valueObj), [valueObj]);

その他のフックで依存値を使用するものは、この方法を使用できます

ただしプロパティを個々に渡しても問題ないなら、オブジェクトを分解して渡しましょう。

補足:memo API

memo関数は、コンポーネントをmemo化するAPIです。
※フックではありません。

コンポーネントを引数として渡すことでメモ化します。
useMemoと異なり、引数に依存値が存在しません。

その代わりに、コンポーネント生成時の属性(props)が依存値として比較されます。

import {  useState, useMemo ,memo} from "react";

  // コンポーネントのメモ化
const UlList = memo( function({texts}) {
  const liItem = texts.map( (text,index)=><li key={index}>{text}</li> );
  return (<ul>
    {liItem}
  </ul>);
});

// Appコンポーネント
function App() {
  const [count,setCount] = useState(1);
  const texts = useMemo( // オブジェクト(配列)の安定化
    ()=>Array.from( {length:count} , (v,index)=>(index+1).toString() ) ,
     [count]
  );

  const clickHandler = () => {
    setCount(count + 1);
  }
  return (<>
    <UlList texts={texts} />
    <button onClick={clickHandler}>Click!!</button>
  </>);
};

上記のコードは useMemoでキャッシュしたオブジェクト(今回は配列)を、memo APIでメモ化したコンポーネントに渡しています。
useMemoでキャッシュせずに呼び出しごとにオブジェクトを生成すると、異なるオブジェクトと判定され、毎回コンポーネントが実行されるからです。

memo化したコンポーネントにオブジェクトを渡す場合、useMemoで参照を安定させよう!

ただし、コールバック関数等の関数をmemo APIでメモ化したコンポーネントに渡す場合は、useMemoではなく、useCallbackフックでキャッシュする必要があります。

useCallbackフック

useCallback関数は、関数をキャッシュすることを目的とした Reactフックです。
依存値を引数として受け取るReactフックの依存値や、memo APIでメモ化したコンポーネントの属性に関数を渡すときは、useCallback関数を使用します。

構文

const cachedValue = useMemo(calculateValue, dependencies)
  1. calculateValue:
    何らかの値を返す関数
  2. dependencies:
    コンポーネント関数で使用している変数で、calculateValueで使用するものを配列で指定。
cachedValue :
前回のdependenciesと同じdependenciesが指定されたら、キャッシュの値。
異なるならcalculateValueを実行した結果

コード例

メモ化したコンポーネントにコールバック関数を渡しています。

import {  useState, useMemo ,memo, useCallback} from "react";

  // コンポーネントのメモ化
const UlList = memo(function({texts,clickHandler}) {
  const liItem = texts.map( (text,index)=><li key={index}>{text}</li> );
  console.log( "関数が呼ばれた!" );
  return (<ul>
    {liItem}
    <button onClick={clickHandler}>Click!!</button>
  </ul>);
});

// Appコンポーネント
function App() {
  const [count,setCount] = useState(1);
 
  const texts = useMemo( 
    ()=>Array.from( {length:count} , (v,index)=>(index+1).toString() ) ,
     [count]
  );

  const clickHandler = useCallback( () => {
    setCount(count + 1);
  },[count]);

  return (<>
    <UlList texts={texts} clickHandler={clickHandler} />
  </>);
};

useEffectフック

useEffect関数は、実DOM反映後に関数を呼び出すことを目的とした Reactフックです。
セットアップ関数とクリーンアップ関数を定義でき、次のタイミングで呼び出されます。

  1. コンポーネントのマウント時:セットアップ関数が実行される
  2. コンポーネントのアンマウント時:クリーンアップ関数が実行される
  3. 依存値の更新時:マウント時に指定したクリーンアップ関数が実行された後、今回指定したセットアップ関数が実行される

マウント時のクリーンアップ関数にはマウント時の変数が、今回指定したセットアップ関数には今回の変数が関連付けられているよ!

useEffecはReactがカバーしている用途外の機能、例えば外部APIやストレージアクセスなどと同期することを目的としています。
この用途以外での使用は推奨されません。

また多用することで、依存関係が複雑に絡み合い、いつ実行されるのか分かりにくくなります。
そのため、まずはレンダー内のロジックや他のフックで実現できないかを検討してから、使用するようにしましょう。

構文

useEffect(setup, dependencies)
  1. setup:
    クリーンアップ関数を返す関数

  2. dependencies:省略可能
    コンポーネントで使用している変数で、setup関数内で使用しているものを配列で列挙する。

    依存値の更新が必要ない場合は、空の配列を指定する。
    dependenciesを省略した場合は、コミットに毎回setup関数が実行される。

なし

コード例1:新規Windowの開け閉め

// Appコンポーネント
function App() {
  const [openFlg,setOpenFlg] = useState(false);

  return (<>
    { openFlg && <WindowOpen /> }
    <button onClick={
      ()=>setOpenFlg(!openFlg)
    } >{openFlg ? "閉じる" : "開く" }</button>
  </>);
};

function WindowOpen(){
  useEffect( ()=>{
    const targetWindow = window.open("", "_blank");
    window.focus();
    return ()=>{
      targetWindow.close();
    };
  },[]);
  return <p>新規Window表示中</p>;
}

コード例2:ルートコンポーネント外のDOM操作

ルートコンポーネント外(React適用外のエリア)のDOM要素を操作する。

function App() {

  useEffect( ()=>{
    const status = document.getElementById("status");
    status.innerText = "アプリ実行中";
  },[]);

};

NGコード例1:statusを依存値に含める

次のコードは、無限ループします。

function App() {

  const [count,setCount] = useState(0);
  useEffect( ()=>{
      setCount( count + 1 );
  },[count]);

};

次の流れになっています。

setCount()で現在値を更新

useEffect()の依存値が更新されたのでセットアップ関数実行

setCount()で現在値を更新

useEffect()の依存値が更新されたのでセットアップ関数実行

setCount()で現在値を更新

無限ループ

次のように変更すれば無限ループを回避できますが、警告が出ます。

  useEffect( ()=>{
      setCount( count + 1 );
  },[]);

Error: Calling setState synchronously within an effect can trigger cascading renders
エラー: エフェクト内で setState を同期的に呼び出すと、カスケードレンダリングが発生する可能性があります。

setTimeout等で非同期に呼び出すのはOKだけど、コールバック関数内で直接(同期的)に読んじゃダメよ。という意味ですね。
ユーザアクション等のクッションがあるならともかく、連続で呼ばれるて同じ処理するなら一回にまとめた方がいいんじゃない?という意味でもあります。

最初のバージョンも同期的なんだけど、たまたまエラーが表示されていなかったみたい。

setCountに関数を渡すとOKと解説しているサイトもありますが、同期的なのには変わりありません。

useContextフック / createContext API

useContext関数は、コンテクスト(グローバル変数のようなもの)をツリー内のコンポーネントで共有することを目的とした、Reactフックです。
ただし、コンテクストは読み取り専用です。
※オブジェクトを指定した場合プロパティ値を変更できますが、非推奨です。

createContext関数は、コンテクストを作成するためのAPI関数です。

深い階層の子コンポーネントに変数を渡す場合、通常は各階層で下階層にリレーで渡していきます。
しかし記述もれの可能性や変数名変更時の作業コストが高いこと等、様々な問題があります。

ようするにメンドクサイ

コンテクストを使用することで、利用するコンポーネントのみでstate変数を共有できます。

構文:useContextフック

const value = useContext(SomeContext)
  1. SomeContext:createContext APIの戻り値(コンテクストオブジェクト)
コンテクストの値

構文:createContext API

const SomeContext = createContext(defaultValue)
  1. defaultValue:規定値

    useContext関数呼び出し時、ツリー状の親要素にコンテクストプロバイダが無い場合に返す値

コンテクストオブジェクト

※戻り値を受け取る変数の名前は、大文字で始める必要があります。

コード例1:基本形

コンテクストの作成

createContext()の戻り値(コンテクストオブジェクト)をエクスポートします。

mycontext.js

import { createContext } from "react";

export const MyContext = createContext("テスト");
コンテクストプロバイダでラップ

createContext()の戻り値をJSXのタグとして使用(コンテクストプロバイダ)して、value属性でコンテクストの値を指定します。

App.js

import { MyContext } from "./mycontext";
import ContextChild from "./contextchild";

// Appコンポーネント
function App() {
  const text = "コンテクストのテスト";
  return (
    <MyContext value={text}>
      <ContextChild />
    </MyContext>
  );

};

export default App
コンテクストの受け取り

useContext()で、コンテクストの値を取得します。

contextchild.jsx

import { useContext } from "react";
import { MyContext } from "./mycontext";

function ContextChild(){
    const text = useContext(MyContext);
    return <p>{text}</p>
}

export default ContextChild;

コード例2:state変数のコンテクスト化

mystate.js

import { createContext } from "react";

export const MyState = createContext(null);

App.jsx

import { useState } from "react";
import { MyState } from "./mystate";
import ContextChild from "./contextchild";

// Appコンポーネント
function App() {
  const [count, setCount] = useState(0);
  return (
    <MyState value={{count,setCount}}>
      <p>{count}回クリックしました。</p>
      <ContextChild />
    </MyState>
  );

};
export default App

contextchild.jsx

import { useContext } from "react";
import { MyState } from "./mystate";

function ContextChild() {
    const { count, setCount } = useContext(MyState);
    return (
        <button onClick={() => setCount(count + 1)}>
            click!!
        </button>
    );
}

export default ContextChild;

 

マウントとアンマウントとコミット

マウント:コンポーネントが作成され画面(DOM)に追加する処理
アンマウント:画面から削除され関連するReactフックのデータが破棄される処理
コミット:コンポーネントの変更点等を画面(DOM)に適用する処理

コンポーネントが状況に応じて異なるReact要素を返す場合、マウントとアンマウントが頻繁に呼び出される可能性があります。

import { useState } from "react";

// Appコンポーネント
function App() {
  const [flg, setFlg] = useState(true);
  const clickHandler = () => { setFlg(!flg); };
  const p = flg ? <P1 /> : <P2 />;
  return (<>
    {p}
    <button onClick={clickHandler}>Change!!</button>
  </>);
}
// P1コンポーネント
function P1() {
  const [count, setCount] = useState(0);
  const clickHandler = () => { setCount(count + 1); };
  return (<>
    <p>段落1({count})</p>
    <button onClick={clickHandler}>Click!!</button>
  </>)
}
// P2コンポーネント
function P2() {
  const [count, setCount] = useState(0);
  const clickHandler = () => { setCount(count + 1); };
  return (<>
    <p>段落2({count})</p>
    <button onClick={clickHandler}>Click!!</button>
  </>)
}
export default App

実行結果:


上記のコードはボタンが押される度に、P1とP2を切り替えています。
このとき、どちらか一方がアンマウントされ、もう一方がマウントされます。

アンマウント時にuseState()のデータが破棄されるため、再度マウントされるとカウントが0から始まります。

コンポーネント内でstyleやクラスをセットして非表示にすることでアンマウントを避けることができます。

更新日:2025/12/25

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。