【React】HTMLにscriptタグでjsonデータを埋め込んでそれを取得して使う方法

2023年10月19日React

HTMLにscriptタグでjsonデータを埋め込んでそれを取得して使う方法を紹介します。
apiを叩いて取得するのではなく、phpなどであらかじめjsonデータをHTMLに落としてそれをJavaScriptで取得して使う方法を紹介します。
以下はReactの例ですが、別にReactでなくても利用できます。

React v19の最新APIであるuseフックとServer Componentsを使った実例を詳しく解紹介しています。

サーバーサイドがPHPやrubyなどの言語の場合

サーバーがPHPやrubyなどの言語の場合は、以下のように役割分担します。

  • PHP: サーバーサイドでJSONデータを生成してHTMLに埋め込む。
  • React: クライアントサイドで埋め込まれたJSONデータを取得し、表示や処理を行う。

シンプルなサイトや、すでにPHPで構築されたシステムがある場合や、データ取得にサーバーレンダリングを必要としない場合におすすめです。

サーバーサイドがNode.jsの場合

React v19での最新のAPIであるuseフックとServer Componentsを使えば、非同期データを効率的に扱うことが可能です。
動的で高速なWebアプリが必要な場合や、ReactのServer Componentsや新しいエコシステムを活用したい場合におすすめです。

htmlにscriptタグでjsonデータを埋め込んでそれを取得して使いたいとき

HTMLにscriptをタグを準備する

<!-- JSONデータを埋め込む -->
<script type="application/json" id="catalog-data">
[
  {
    "categoryId": 1,
    "categoryName": "総合カタログ",
    "items": [
      {
        "id": 1,
        "title": "総合カタログ",
        "imgSrc": "/assets/images/catalog/img01.jpg",
        "text": "カタログの内容を、説明する内容が入ります。"
      }
    ]
  },
  ...
]
</script>

上のように、type="application/json"をscriptタグにつけるのがポイントです。
これでjson形式で埋め込むことが可能になります。

Reactで取得(JavaScriptであればなんでも可)

import React, { useEffect, useState } from 'react';
import SearchForm from './SearchForm';
import CatalogCategory from './CatalogCategory';
import useCatalogData from './helpers/useCatalogData';

// カタログ一覧のコンポーネント
function Catalog({ cartItems, onAddToCart }) {
  const catalogData = useCatalogData();

  useEffect(() => {
    // カタログリンククリック時のイベントハンドラー
    const handleCatalogLinkClick = event => {
      event.preventDefault();
      const catalogId = event.currentTarget.getAttribute('data-catalog-id');

      // セッションストレージからカタログIDの配列を取得。存在しない場合は新しい空の配列を作成
      const catalogIds = JSON.parse(sessionStorage.getItem('catalogIds')) || [];

      // catalogId を数値に変換して一意か調べる
      const numericCatalogId = parseInt(catalogId, 10);

      if (!catalogIds.some(id => parseInt(id, 10) === numericCatalogId)) {
        // カートアイテムを更新
        onAddToCart(numericCatalogId);

        // catalogId を追加
        catalogIds.push(numericCatalogId);

        // カタログID一覧をセッションストレージに保存
        sessionStorage.setItem('catalogIds', JSON.stringify(catalogIds));
      }
    };
    // カタログリンク要素を取得
    const catalogLinks = document.querySelectorAll('.js-catalog-link');
    // カタログリンククリック時のイベントリスナーを設定
    catalogLinks.forEach(catalogLink => {
      catalogLink.addEventListener('click', handleCatalogLinkClick);
    });

    // コンポーネントがアンマウントされる際にイベントリスナーをクリーンアップ
    return () => {
      catalogLinks.forEach(catalogLink => {
        catalogLink.removeEventListener('click', handleCatalogLinkClick);
      });
    };
  }, []);

  return (
    <div className="contents__main">
     ...
    </div>
  );
}

export default Catalog;

こちらの例では、カタログの一覧データを取得したいときに、HTMLの中にそのデータをjson形式で落とします。
そして、それを取得するためのコードです。

helpers/useCatalogData.js

/**
 * カタログデータを取得するカスタムフック
 *
 * @returns {Object} カタログデータのオブジェクト
 */
function useCatalogData() {
  const catalogDataJSON = document.getElementById('catalog-data').textContent;
  const catalogData = JSON.parse(catalogDataJSON);

  return catalogData;
}

export default useCatalogData;

そして、helpersなどのディレクトリにデータを取得するカスタムフックを作成すればOKです。

React v19のStrict Modeによる再レンダリングの影響

React v19ではStrict Modeがデフォルトで強化され、一部の副作用が2回実行されることがあります。useCatalogDataカスタムフックを使う際、DOMからデータを取得する操作(document.getElementById)が重複実行される可能性があります。この影響を受けないように、一度だけ取得するように修正する必要があります。

修正例:
useCatalogDataフックでuseRefを利用し、JSONデータを一度だけパースします。

import { useRef } from 'react';

function useCatalogData() {
  const catalogDataRef = useRef(null);

  if (!catalogDataRef.current) {
    const catalogDataJSON = document.getElementById('catalog-data').textContent;
    catalogDataRef.current = JSON.parse(catalogDataJSON);
  }

  return catalogDataRef.current;
}

export default useCatalogData;

メタタグの活用

JSONデータをscriptタグではなく、metaタグを使って埋め込む方法も検討できます。これは、SEOへの影響を抑えつつ、JSONデータをHTML内に埋め込むもう一つの方法です。

メタタグ例:

<meta id="catalog-data" name="catalog-data" content='[{"categoryId":1,"categoryName":"総合カタログ","items":[{"id":1,"title":"総合カタログ","imgSrc":"/assets/images/catalog/img01.jpg","text":"カタログの内容を、説明する内容が入ります。"}]}]'>

メタタグから取得するカスタムフック:

function useCatalogData() {
  const catalogDataJSON = document.querySelector('meta[name="catalog-data"]').getAttribute('content');
  return JSON.parse(catalogDataJSON);
}

export default useCatalogData;

React v19の最新API: Server Components と use フックの概要

Server Components: サーバーサイドで実行されるReactコンポーネント。クライアントに送信されるHTMLを効率的に生成します。

主に重い処理や非同期データ取得に向いています。
クライアントのバンドルサイズを削減します。
use フック: React v19で導入された非同期データを直接処理する新しいフックです。

サーバーサイドやクライアントサイドでPromiseを簡単に扱えます。
データのローディング状態を明示的に管理する必要がありません。

デモアプリ: カタログデータを非同期で取得して表示する

サーバーサイドで非同期データを取得する例

まず、useフックを利用して、サーバーサイドでカタログデータを取得し、それをクライアントに渡す方法を解説します。

Server Components

// src/components/CatalogList.server.js

import { use } from "react";

// 非同期データを取得する関数
async function fetchCatalogData() {
  // サーバー上でデータを取得 (例: API やデータベースから取得)
  const response = await fetch("https://api.example.com/catalog");
  if (!response.ok) {
    throw new Error("Failed to fetch catalog data");
  }
  return response.json();
}

// Server Component
export default function CatalogList() {
  // `use` を利用して非同期データを取得
  const catalogData = use(fetchCatalogData());

  return (
    <ul>
      {catalogData.map((category) => (
        <li key={category.categoryId}>
          <h3>{category.categoryName}</h3>
          <ul>
            {category.items.map((item) => (
              <li key={item.id}>
                <img src={item.imgSrc} alt={item.title} />
                <p>{item.text}</p>
              </li>
            ))}
          </ul>
        </li>
      ))}
    </ul>
  );
}

ルート設定 (App.server.js)

// src/App.server.js
import CatalogList from "./components/CatalogList.server";

export default function App() {
  return (
    <main>
      <h1>Catalog</h1>
      <CatalogList />
    </main>
  );
}

クライアントサイドの組み込み

// src/index.client.js
import { hydrateRoot } from "react-dom";
import App from "./App.server";

// サーバーレンダリングされたAppをクライアントでハイドレート
hydrateRoot(document.getElementById("root"), );

サーバーコンポーネントなしでクライアントでの非同期処理をする場合

クライアントサイドで同じような非同期処理をするにはuseEffectを使いますが、useフックを使うとコードがさらに簡素化できます。

クライアントコンポーネント例

// src/components/CatalogList.client.js

import { use } from "react";

// 非同期データ取得関数
async function fetchCatalogData() {
  const response = await fetch("https://api.example.com/catalog");
  if (!response.ok) {
    throw new Error("Failed to fetch catalog data");
  }
  return response.json();
}

export default function CatalogList() {
  const catalogData = use(fetchCatalogData());

  return (
    <ul>
      {catalogData.map((category) => (
        <li key={category.categoryId}>
          <h3>{category.categoryName}</h3>
          <ul>
            {category.items.map((item) => (
              <li key={item.id}>
                <img src={item.imgSrc} alt={item.title} />
                <p>{item.text}</p>
              </li>
            ))}
          </ul>
        </li>
      ))}
    </ul>  );
}

useの使用条件

useフックは非同期データを取得する際、Promiseを直接扱うため、簡素なコードが書けます。
useはReact v19以上でしか利用できません。

Server Componentsの強み

サーバーサイドでHTMLを生成するため、初期表示が高速で、SEOにも適しています。

クライアントとサーバーの分離

Server Componentsは、サーバーでのみ実行されるロジック(データベースアクセスなど)を切り離して記述できます。

React

Posted by devsakaso