WordPress Logo データへのアクセス @wordpress/core-data

ダイナミックブロックの作成などで WordPress のデータに JavaScript でアクセスする際に使用する @wordpress/core-data パッケージについての覚書です。

select 関数や useSelect フック、getEntityRecords セレクターの基本的な使い方や取得したデータをレンダリングする方法なども掲載しています。

更新日:2025年02月20日

作成日:2025年01月26日

関連ページ

@wordpress/core-data

投稿やカテゴリーなどの情報を使用するダイナミックブロックを作成する場合、エディター側(edit.js)では JavaScript でそれらのデータにアクセスする必要があります。

JavaScript から WordPress のデータにアクセスするには、WordPress REST API を利用します。

WordPress REST API は、WordPress 外部からウェブサイトのデータを取得できる仕組みで、投稿や固定ページ、タクソノミー、その他のカスタムデータタイプを表す REST エンドポイント (URI) を提供します。

ブラウザでサイトの URL に /wp-json/wp/v2/posts を付けてアクセスするとその URI(この場合は投稿のエンドポイント)に GET リクエストを送信し、JSON レスポンスを返します。

例えば、以下のような投稿のデータが返されます。

上記は Firefox での表示例です。Chrome の場合、上記のように整形して表示するにはブラウザ拡張機能 JSON Formatter を利用することができます。

関連情報

REST API からデータにアクセスする方法はいくつかありますが、ブロック開発では @wordpress/core-data パッケージと呼ばれるデータにアクセスするための組み込みの機能を利用できます。

@wordpress/core-data パッケージは @wordpress/data パッケージの上に構築されており、WordPress REST API からデータを取得・操作する機能を提供します。

@wordpress/data パッケージ

@wordpress/data は、WordPress におけるデータの状態を管理する基本的なフレームワークで、以下のような機能を提供します。

  • ストアの登録: 状態を管理するストアを定義します。
  • データの取得: select 関数や useSelect フックで状態を取得します。
  • データの更新: dispatch 関数や useDispatch フックを使って状態を変更(更新)します。
  • 状態の監視: サブスクリプション機能を通じて状態の変更を監視します。

@wordpress/data は汎用的な状態管理ライブラリで、どのようなデータでも管理できる仕組みを提供しています。

@wordpress/core-data パッケージ

@wordpress/core-data は、 @wordpress/data を利用して構築されたライブラリで、WordPress REST API を使ってサイトや投稿のデータを操作するための機能(高レベルの抽象化)を提供する WordPress のエンティティ(投稿、ページ、タクソノミー、設定など)を管理するためのパッケージです。

以下のような特徴があります。

  1. REST API の統合

    • @wordpress/core-data は WordPress の REST API を通じて、投稿、ページ、ユーザー、メディア、設定などのデータを管理します。
    • REST API のエンドポイントに関する詳細を隠蔽し、より簡単にデータの取得・更新を行えるようにします。
  2. 事前定義されたストア

    • @wordpress/core-data は、@wordpress/data の仕組みを使って「特定のストア」を登録します。各ストアは core、core/editor、core/block-editor のように、固有の名前空間を持ち、例えば以下のようなストアが定義されています(主なストア)。
      • core ストア: サイト全体の設定や情報
      • core/editor ストア: ブロックエディター(投稿編集)の状態
      • core/block-editor ストア: ロックエディターの状態(ブロックデータ)
      • ccore/notices ストア: 通知メッセージの管理

    各ストアのリファレンス一覧:Data Module Reference

  3. セレクターとディスパッチャー

    • REST API を抽象化したセレクター(データ取得用の関数)とディスパッチャー(データ操作用の関数)が事前に用意されています。
@wordpress/data と @wordpress/core-data
特徴 @wordpress/data @wordpress/core-data
役割 汎用的な状態管理システム WordPress のエンティティ管理ツール
提供機能 ストアの登録、セレクター・アクションの定義 投稿、ページ、カテゴリーなどの操作用 API
REST API の統合 なし REST API を抽象化して統合済み
ストアの登録 開発者が必要に応じてストアを定義 WordPress 用に事前登録されたストアを提供
グローバル wp での利用 wp.data グローバルでは wp.data.select('core') で利用

ストア

ストアとはデータを保存し、操作するための場所(データを保存して使いやすくするための仕組み)です。

アプリケーション全体で共有したいデータを一元管理する役割を果たします。ストアを使うことで、どの部分からでも同じデータにアクセスしたり、それを更新したりすることが簡単になります。

普通の JavaScript でもデータは管理できますが、大きなアプリケーションになると次のような問題が出てきます。

  1. データがどこにあるのかわからない
    • たくさんの場所でデータが管理されると、どれが最新なのかわかりません。
  2. データの更新が大変
    • データを更新するたびに、他の部分にもその影響を伝えるのが手間です。
  3. コードが複雑になる
    • アプリ全体の状態がバラバラに管理されると、修正が難しくなります。

ストアを使えば、これらの問題を解決できます。

  • すべてのデータを一箇所にまとめることで、「データの中央管理」が可能になります。
  • データの変更を他の部分に自動的に通知できる仕組みが用意されています
WordPress におけるストア

WordPress のストアは、@wordpress/data パッケージで管理されています。この仕組みを使うと、テーマやプラグインの開発でよく使うデータ(例えば投稿やページの情報)を効率よく操作できます。

  • ストアの主な役割:
    1. データの保存場所
      • 例: 投稿データ、カテゴリー情報、現在のユーザー情報など。
    2. データの取得
      • セレクターを使って、ストアからデータを取得します。
    3. データの更新
      • ディスパッチャーを使って、ストアに保存されたデータを更新します。

大雑把に言うと、@wordpress/core-data パッケージを利用すると(REST API の詳細を直接扱わずに)、ストア(WordPress のデータ状態管理システム)やセレクターなどの特別な関数を使用してデータにアクセスしたり操作することができるということになります。

以下のページが参考になるかもしれません。

@wordpress/dataを使ってアプリケーションの状態管理を行う方法

ブラウザのコンソールで確認

投稿の編集画面で開発ツールを表示し、コンソールタブに以下を入力して return キーを押すと core ストアにアクセスします。

wp.data.select("core")

wp.data は @wordpress/data パッケージを指します(wp.data は @wordpress/data パッケージをグローバルな wp オブジェクト上にエクスポートしたものです)。

ストアにアクセスするには select 関数の引数にストアの名前空間(この例では core)を指定します。

つまり、上記は wp.data(@wordpress/data パッケージ)の select 関数を使って core ストア(@wordpress/core-data に含まれるストア)にアクセスしています。

結果は、core ストアからデータを取得するために使用できる関数(セレクター)を含むオブジェクトが返されます(左端のアイコン ▶ をクリックして展開すると詳細を確認できます)。

wp.data.select('core') は core ストアにアクセスして データを取得するためのインターフェースで、@wordpress/core-data が提供する機能を利用する窓口と考えることができます。

上記で返されたセレクターの1つ getEntityRecords を使うと WordPress REST API を通じて特定のエンティティ(投稿、ページ、カテゴリーなど)のレコードにアクセスすることができます。

以下は投稿データにアクセスする例です。

getEntityRecords のパラメータに 'postType', 'post' を指定して投稿のエンティティのレコードにアクセスします。また、クエリパラメータとして {per_page: 5} を指定しているので、5件の投稿のレコードが返されます。

wp.data.select('core').getEntityRecords('postType', 'post', {per_page: 5})

以下は実行例です。Rest API にリクエストを送信すると、リクエストが完了するまでレスポンスは null になるため、実行して null が返った場合は、もう一度実行します。

上記を実行して得られる結果と、REST API の /wp-json/wp/v2/posts?per_page=5 で得られる結果は、基本的に同じデータを返しますが、以下のような違いがあります。

特性 /wp-json/wp/v2/posts wp.data.select('core').getEntityRecords
データ取得 REST API から直接取得 REST API を介してストア経由で取得
キャッシュ なし ストア内にキャッシュされる
HTTP レスポンス情報 完全なレスポンス情報を取得可能 レスポンス情報は抽象化されている
WordPress 環境依存 依存しない WordPress フロントエンド環境が必要
再利用性 低い 高い
リアクティブ性(状態管理) 自分で実装する必要がある 自動的に対応

以下は JavaScript の fetch() を使って REST API から上記とほぼ同じデータを取得する例です。

fetch('http://localhost/xxxxx/wp-json/wp/v2/posts?per_page=5')
  .then((response) => response.json())
  .then((posts) => {
      console.log(posts);
  });

以下は WordPress の @wordpress/fetch-api を使う場合の例です。

@wordpress/api-fetch は WordPress 特化型の HTTP クライアントで、REST API を簡単に扱うために設計されています。fetch() を内部的に使用しつつ、WordPress 固有の拡張機能を提供します。例えば、自動的に WordPress REST API のルート(/wp-json)を補完してくれます。

import apiFetch from '@wordpress/api-fetch';

apiFetch({ path: '/wp/v2/posts?per_page=5' })
  .then((data) => console.log(data))
  .catch((error) => console.error(error.message));

データへのアクセスとレンダリング

ブロック開発では、@wordpress/data の select 関数や useSelect フックを使って、@wordpress/core-data の各ストアからデータを取得することができます。

ダイナミックブロックの作成では、投稿データやカテゴリー情報などのデータにアクセスして動的なコンテンツを作成できます。

例えば、useSelect フックを使用すると、状態が変化した際にコンポーネントが自動的に再レンダリングされるため、リアクティブなデータ管理が可能です。

以下は core ストアの投稿データにアクセスして、5件の投稿のタイトルを表示する例です。

データにアクセスするには、select 関数を使ってデータを取得したいストアにアクセスします。そしてストアに用意されているセレクターを使ってデータにアクセスします。

但し、その際、select 関数を直接呼び出すのではなく、useSelect フックを使用します。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
// @wordpress/data から useSelect をインポート
import { useSelect } from "@wordpress/data";

export default function Edit() {

  // core ストアから投稿データを5件取得
  const posts = useSelect(
    (select) =>
      // select 関数で core ストアにアクセスして getEntityRecords セレクターで投稿データを5件取得
      select("core").getEntityRecords("postType", "post", { per_page: 5 }),
    [],
  );

  return (
    <ul {...useBlockProps()}>
      {posts &&
        posts.map((post) => <li key={post.id}>{post.title.rendered}</li>)}
    </ul>
  );
}

ブロック開発環境

このページに掲載されているコードを試すには、ブロックの開発環境が必要です。

以降では以下の create-block コマンドを使って作成したダイナミックブロックのひな型を使用しています。

% npx @wordpress/create-block@latest --variant=dynamic --namespace wdl-block my-dynamic-block

上記コマンドをプラグインディレクトリで実行すると、以下のようなファイルが作成されます。

📁 my-dynamic-block
├── .editorconfig
├── .gitignore
├──📁 node_modules
├── package-lock.json
├── package.json
├── readme.txt
├──📁 build
├── my-dynamic-block.php
└──📁 src
    └──📁 my-dynamic-block
        ├── block.json
        ├── edit.js  // このファイルを使用してコードを試すことができます。
        ├── editor.scss
        ├── index.js
        ├── style.scss
        ├── view.js
        └── render.php

プラグインを有効化して、作成したプラグインのディレクトリで npm start を実行し、src ディレクトリの edit.js ファイルを使ってコードを試すことができます。

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';

export default function Edit() {
  return (
    <p { ...useBlockProps() }>
      { __(
        'My Dynamic Block – hello from the editor!',
        'my-dynamic-block'
      ) }
    </p>
  );
}

関連ページ:WordPress ブロック開発の環境構築(Create Block ツールについての覚書)

@wordpress/core-data の主なストア

@wordpress/core-data には、WordPress REST API を通じてさまざまなエンティティ(投稿、ページ、ユーザーなど)を操作するためのストアが登録されています。それぞれのストアは、WordPress 内の特定のデータ種別や操作に対応しています。

  • 投稿データや REST API エンティティを操作 → core ストア
  • ブロックエディターの状態を操作 → core/block-editor ストア
  • 通知やエディター設定を操作 → core/notices や core/edit-post ストア

以下は、@wordpress/core-data に含まれる主なストアとその概要です。

core ストア

WordPress の標準的な REST API エンティティ(投稿、ページ、タクソノミー、ユーザーなど)を管理。

操作できるデータ:

  • 投稿タイプ(postType)
  • タクソノミー(taxonomy)
  • 投稿(post)
  • ページ(page)
  • ユーザー(user)
  • コメント(comment)
  • メディア(media)

使用例(※ useSelect フック内で使用)

select('core').getEntityRecords('postType', 'post', { per_page: 5 }); // 最新の投稿を5件取得
select('core').getEntityRecord('postType', 'post', 123); // ID 123 の投稿を取得
select('core').getPostTypes( { per_page: -1 } ); // 現在登録されている投稿タイプを取得
select('core').getTaxonomies(); // 現在登録されているタクソノミーを取得
select('core').getTaxonomies( { type: 'post' } ); // 投稿のタクソノミー(カテゴリーとタグ)を取得

core ストアのセレクター: WordPress Core Data

core/edit-post ストア

投稿編集画面での状態管理を担当。

操作できるデータ:

  • 投稿エディターの設定
  • サイドバーやツールバーの状態
select('core/edit-post').isEditorSidebarOpened(); // 設定サイドバーが開いているかどうか

isEditorPanelOpened など、いくつかのセレクターは editor ストアに移動されています。

詳細:Unification of the site and post editors in 6.5

core/edit-post ストアのセレクター: The Editor’s UI Data

core/editor ストア

投稿エディターの状態をより細かく管理。

操作できるデータ:

  • 編集中の投稿のデータ(タイトル、本文など)
  • 保存状態やエラー
select('core/editor').getEditedPostAttribute('title'); // 編集中の投稿タイトルを取得
select('core/editor').isSavingPost(); // 現在投稿を保存しようとしているかどうかの状態を取得
select('core/editor').getCurrentPostId() // 現在表示している投稿の ID

core/editor ストアのセレクター: The Post Editor’s Data

※ getBlocks() など一部のブロック関連のセレクターは core/block-editor に移されています。

core/block-editor ストア

ブロックエディター(Gutenberg)の状態を管理。

操作できるデータ:

  • 現在のブロックの状態
  • ブロックの選択やフォーカス
  • ブロックの属性
  • 様々な設定の取得
select('core/block-editor').getSelectedBlock(); // 現在選択されているブロックを取得
select('core/block-editor').getSettings(); // 設定に関するオブジェクトを取得

core/block-editor ストアのセレクター: The Block Editor’s Data

core/notices ストア

アプリケーション内の通知(ノーティス)の管理を担当。

select('core/notices').getNotices(); // 通知を取得

core/notices ストアのセレクター: Notices Data

select 関数

@wordpress/data パッケージの select 関数は、WordPress の状態管理システムである「データストア」から現在の状態(ステート)を取得するために使われます。

基本的な使い方

select 関数を使うにはどのストアからデータを取得したいのかを指定します。以下は基本的な構文です。

引数にストア名を指定すると、そのストアのセレクターが含まれるオブジェクトを返します。

このオブジェクト(以下の store)を使って、ストアに定義されているセレクター(データ取得用の関数)を呼び出します。

// @wordpress/data パッケージから select 関数をインポート
import { select } from '@wordpress/data';

// 特定のストアから状態(ストアのセレクターが含まれるオブジェクト)を取得する
const store = select('store-name');

セレクターとは?

セレクターは、ストアから特定のデータを取得するための関数です。各ストアには、それぞれ専用のセレクターが用意されています。例えば、'core/editor' ストアには以下のようなセレクターがあります。

  • getEditedPostAttribute(attributeName) : 投稿の特定の属性を取得
  • isSavingPost() : 現在投稿を保存中かどうかを取得
  • getCurrentPostType() : 現在の投稿の投稿タイプを取得

edit.js に以下のコード(5,9,10行目)を追加すると、コンソールには core/editor ストアからデータを取得するために使用できるセレクターを含むオブジェクトが出力されます。

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';
// select 関数をインポート
import { select } from '@wordpress/data';

export default function Edit() {
  // core/editor ストアから状態(全てのセレクターを含むオブジェクト)を取得
  const store = select('core/editor');
  console.log(store);

  return (
    <p { ...useBlockProps() }>
    {__("My Dynamic Block – hello from the editor!", "my-dynamic-block")}
    </p>
  );
}

上記により、以下のようにコンソールに全てのセレクターを含むオブジェクトが出力されます。※ 投稿にこのブロック(My Dynamic Block)が挿入されている必要があります。

これらのセレクターは The Post Editor’s Data に掲載されています。

データにアクセスするには、select("ストア名").セレクター() のように使います。

以下は、select 関数で core/editor ストアにアクセスして getEditedPostAttribute() セレクターを使って編集中の投稿タイトルを取得する例です。

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';
import { select } from '@wordpress/data';

export default function Edit() {
  // core/editor ストアの getEditedPostAttribute() セレクターを使って投稿タイトルを取得
  const postTitle = select("core/editor").getEditedPostAttribute("title");
  // 現在の投稿タイトルをコンソールに出力
  console.log(postTitle);

  return (
    <p {...useBlockProps()}>
      {__("My Dynamic Block – hello from the editor!", "my-dynamic-block")}
    </p>
  );
}
select 関数の引数

select 関数の引数にはストア名(名前空間)を指定する方法と、インポートしたストアオブジェクトを直接渡する方法があります。

以下は前述の例をストアオブジェクトを直接渡する方法で書き換えたものです。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { select } from "@wordpress/data";
// core/editor のストアオブジェクトをインポート
import { store as coreEditorStore } from '@wordpress/editor';

export default function Edit() {
  // select 関数にストアオブジェクトを渡す
  const postTitle = select(coreEditorStore).getEditedPostAttribute("title");
  console.log(postTitle);

  return (
    <p {...useBlockProps()}>
      {__("My Dynamic Block – hello from the editor!", "my-dynamic-block")}
    </p>
  );
}

ストア名を指定する方法は、シンプルで文字列で参照できるため他のモジュールから利用しやすいというメリットがあります。

ストアオブジェクトを直接指定する方法は、ストアがどこから来ているのか明確で、型定義がある場合、インテリセンスや型チェックの恩恵を受けられる(ストアの型情報や関数の型補完が有効になる)というメリットがあります。但し、ストアを事前にインポートする必要があるため、少し手間がかかります。

プロジェクトの規模や要件によって使い分けるのが良いようです。

  • 小規模プロジェクト : ストア名(文字列)を使うほうが簡単で、依存関係も少なくなります。
  • 大規模プロジェクトや型を利用する場合 : ストアオブジェクトを直接使う方法がおすすめ。コードの保守性や安全性が向上します。
リアルタイムの状態取得

select 関数はストアの状態をリアルタイムに取得します(現在の状態を即座に返します)。そのため、状態が変われば select の結果も変わります。以下が select 関数の特徴です。

  • ストアから現在のデータを同期的に取得します。
  • データの変更を検知して再レンダリングをトリガーする仕組みはありません。
  • 再レンダリングが必要な React コンポーネント内でそのまま使用すると、変更が反映されません。

以下は、select 関数を直接呼び出して現在編集中の投稿のタイトルを取得して表示する例です。

この例の getEditedPostAttribute はエディターで現在編集中の投稿のデータにアクセスするための関数で、この場合、投稿のタイトルはすでにエディターストアに存在している(同期的に保持されている)ため、非同期処理が必要ありません。

また、このコードは単純に一度だけ投稿タイトルを取得し、それをレンダリングしているだけで、投稿タイトルが変更されるときにリアクティブに再レンダリングする必要がない場合のものです。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { select } from "@wordpress/data";

export default function Edit() {
  // 現在編集中の投稿のタイトルを取得
  const postTitle = select("core/editor").getEditedPostAttribute("title");

  return (
    <p {...useBlockProps()}>
      {__(postTitle, "my-dynamic-block")}
    </p>
  );
}

core/editor ストアの多くのデータは同期的に取得可能ですが、一部のデータは非同期で取得されるか、特定の条件下で非同期的に利用されることがあります。

以下は、select 関数を直接呼び出して、core ストアの getEntityRecords セレクターを使って投稿データを取得しようとしていますが、初期レンダリングでは非同期データが間に合わないためデータが取得できません。

select 関数は現在の状態を即座に返しますが、非同期データの取得が完了していない場合には null を返すため、初期レンダリングでは posts が常に null になり、posts.map() は実行されず、ul 要素のみが出力されます。この場合、useSelect フックを使えば投稿データを取得できます。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { select } from "@wordpress/data";

export default function Edit() {
  // 初期レンダリングではデータが取得されない(非同期データが間に合わない)
  const posts = select("core").getEntityRecords("postType", "post", {per_page: 5,});

  // 非同期データが間に合わないので posts は null になるため、posts.map() は実行されない
  return (
    <ul {...useBlockProps()}>
      {posts && posts.map((post) => (
        <li key={post.id}>{post.title.rendered}</li>
      ))}
    </ul>
  );
}

select を直接使用してエラーになる(データが取得できない)ケースは以下の場合です。

  1. 非同期データが関与する場合

    • 例えば上記の、select("core").getEntityRecords("postType", "post") のように REST API を介して非同期に取得されるデータは、初期状態では未ロード(null)になる可能性があります。
  2. リアクティブな再レンダリングが必要な場合

    • select は現在のストアの状態を同期的に取得するだけの関数であり、データが変化しても自動的に再レンダリングをトリガーしません。そのため、投稿タイトルやコンテンツが変更されるたびに UI を更新する必要がある場合は、useSelect を使用する必要があります。

ダイナミックブロックで select を直接使用しない理由

ダイナミックブロックでは、投稿データやカスタムデータなど、非同期データやリアクティブな更新が含まれるケースが一般的です。直接 select を呼び出して使用すると、非同期データが未ロードの場合や状態変化を追跡する必要がある場合に対応できません。

そのため、ダイナミックブロックでは次項で説明する useSelect フックを使用することがほとんどです。

useSelect フックは、select を内部で使用しつつ、状態の変化を自動的に検知し、React コンポーネントのライフサイクルに統合されており、データが変更されるとコンポーネントが再レンダリングされます。

useSelect を使用することで、非同期データを安全に扱い、データの変化に応じたリアクティブな UI 更新を実現できます。

useSelect を優先的に使用

同期か非同期かを意識せず、リアクティブな UI を構築する場合には useSelect を使用するのが安全です。

useSelect フック

@wordpress/data パッケージの useSelect は React のカスタムフックで、指定したストアからリアクティブに状態を取得するために使用します。状態が変化すると、自動的にコンポーネントが再レンダリングされます。

基本的な構文は次の通りです。

// @wordpress/data パッケージから useSelect フックをインポート
import { useSelect } from '@wordpress/data';

const myData = useSelect((select) => {
  // select関数でストアからデータを取得
  return select('store-name').getSomeData();
}, [依存配列]);

前述の、直接 select 関数を呼び出して投稿のデータが取得できなかったコードは以下のように useSelect フックを使えば投稿のデータを取得できます。16行目の posts &&条件付きレンダリングで、posts が true の(null でない)場合にのみ右側の posts.map() が評価されレンダリングされます。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {

  // useSelect のコールバック関数内で select 関数を使い、ストアからデータを取得
  const posts = useSelect(
    (select) => select("core").getEntityRecords("postType", "post", {per_page: 5,}),
    [],
  );

  return (
    <ul {...useBlockProps()}>
      {posts && posts.map((post) => (
        <li key={post.id}>{post.title.rendered}</li>
      ))}
    </ul>
  );
}
useSelect の引数

useSelect は、コールバック関数と依存配列の2つの引数(パラメータ)を取ります。

コールバック関数

useSelect の最初の引数はコールバック関数です。この関数内で引数に渡された select 関数を使い、ストアから必要なデータを取得して返します。

以下は core/editor ストアから投稿タイトルを取得しています。

import { useSelect } from '@wordpress/data';

const myData = useSelect((select) => {
  return select("core/editor").getEditedPostAttribute("title");
});

アロー関数は => の直後の式を自動的に返すので、以下のように return 文と波括弧を省略できます。

const myData = useSelect(
  (select) => select("core/editor").getEditedPostAttribute("title")
);

依存配列

useSelect の第二引数は依存配列です。依存配列を指定することで、特定の値が変化したときにだけコールバック関数が再評価(再実行)されます。

  • 指定しない場合:依存配列を省略または空の配列を指定した場合、コールバック関数は、ストアの状態が変わるたびに再評価されます。
  • 指定する場合:依存関係が少なくなるため、パフォーマンスを最適化できます。
const myData = useSelect(
  (select) => select('core/editor').getEditedPostAttribute('title'),
  [postId] // postId が変わったときのみ再評価
);

※ 第二引数の依存配列は指定することが推奨されます。依存配列を適切に設定することで、不要な再計算を防げます。Always declare useSelect dependencies

複数のデータを取得

useSelect 内で複数のストアやセレクターを組み合わせることもできます。

以下は投稿のタイトル、内容、保存状態を同時に取得する例です。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {

  // useSelect 内で複数のデータを取得
  const { title, content, isSaving } = useSelect((select) => {
    const editor = select("core/editor");
    return {
      title: editor.getEditedPostAttribute("title"),
      content: editor.getEditedPostAttribute("content"),
      isSaving: editor.isSavingPost(),
    };
  }, []);

  return (
    <div {...useBlockProps()}>
      <h3>{title}</h3>
      <div>{content}</div>
      {isSaving && <p>Saving...</p>}
    </div>
  );
}

上記の useSelect は以下のように記述することもできます。

const { title, content, isSaving } = useSelect((select) => {
  // ストアからセレクターを分割代入
  const { getEditedPostAttribute, isSavingPost } = select("core/editor");
  return {
    title: getEditedPostAttribute("title"),
    content: getEditedPostAttribute("content"),
    isSaving: isSavingPost(),
  };
}, []);

異なるストアからデータを取得することもできます。

以下は core/editor ストアからユーザー ID と投稿 ID を取得し、それらを使って現在の投稿と現在のユーザー以外の投稿を除外した投稿データを core ストアから取得する例です。

const {authorDetails, posts } = useSelect(
  (select) => {
    // 現在のユーザーの ID を取得
    const _authorId = select("core/editor").getCurrentPostAttribute("author");
    // ユーザーの ID からユーザーの情報を取得
    const authorDetails = _authorId
      ? select("core").getUser(_authorId)
      : null;
    // 現在の投稿の ID を取得
    const _currentPostId = select('core/editor').getCurrentPostId();
    // パラメータの author に現在のユーザーの ID を、exclude に現在の投稿 ID を指定して投稿を取得
    const posts = select("core").getEntityRecords("postType", "post", {
      author: _authorId,  // 現在のユーザーの投稿を対象に
      exclude: _currentPostId  // 現在の投稿を除外
    });

    return {
      authorDetails: authorDetails,
      posts,
    };
  },
  [],
);

最小限の数の useSelect 呼び出しで、できるだけ多くのデータを読み取るようにすると効率的です(How to work effectively with the useSelect hook)。

条件付きレンダリング

データが非同期で読み込まれる場合:

ストアがまだデータをロードしていない場合、null や undefined が返ることがあります。

例えば、以下は投稿データの取得例ですが、getEntityRecords セレクターは API リクエストを発行して非同期にデータを取得するため、初期レンダリングでは posts が null になります。

以下の19行目の posts && は論理 AND 演算子 (&&) を使用した条件付きレンダリングで、条件が真の場合に JSX(この場合は map() が返す li 要素の配列)をレンダーし、それ以外の場合は何もレンダーしないという場合に使用されます(posts が真の場合にのみ右側の posts.map() が評価されます)。

19行目の posts && がない場合、初回レンダリング時に posts.map() を呼び出すと posts が null なので、エラーが発生してしまい、useSelect フックが再実行されません(エラーで終了します)。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {

  // 非同期データの取得
  const posts = useSelect(
    (select) => select("core").getEntityRecords("postType", "post", {per_page: 5,}),
    [],
  );

  // 初回は null が出力され、その後データが取得されると更新される
  console.log(posts);

  return (
    <ul {...useBlockProps()}>
      {posts && posts.map((post) => (
        <li key={post.id}>{post.title.rendered}</li>
      ))}
    </ul>
  );
}

上記の return ステートメントは、以下とほぼ同じことです。posts が真の場合には、posts.map() が返す li 要素の配列をレンダリングし、そうでなければ null をレンダリングします(何もレンダリングしません)。

return (
  <ul {...useBlockProps()}>
    {posts ? (posts.map((post) => (
      <li key={post.id}>{post.title.rendered}</li>
    ))) : null}
  </ul>
);

また、以下のようにストアがまだデータをロードしていない(posts が null)場合、「Loading...」とローディング中であることを表示し、データが取得できたら表示することもできます。

export default function Edit() {

  const posts = useSelect(
    (select) => select("core").getEntityRecords("postType", "post", {per_page: 5,}),
    [],
  );

  // まだデータをロードしていない(posts が null)場合
  if(!posts) {
    return <p {...useBlockProps()}>Loading...</p>;
  }

  return (
    <ul {...useBlockProps()}>
      {posts.map((post) => (
        <li key={post.id}>{post.title.rendered}</li>
      ))}
    </ul>
  );
}

getEntityRecords

getEntityRecords は @wordpress/core-data パッケージに用意されているセレクターの一つで、WordPress REST API を通じて(非同期に)特定のエンティティ(投稿、ページ、カテゴリーなど)のレコードを取得するために使用します。

このセレクターは API リクエストを発行して、結果をキャッシュし、必要なレコードのリスト(配列)を返します。

getEntityRecords(state, kind, name, query)

以下のパラメータを受け取ります。

state state は WordPress のデータストアの内部状態(内部的な状態ツリー)を表します。通常この引数はセレクターを直接使うときに内部的に処理されるため省略可能で、ほとんどの場合、省略されます。
kind 要求しているエンティティ(データ)の種類。例:'postType' や 'taxonomy' など
  • postType → 投稿タイプ
  • taxonomy → タクソノミー
  • root → ルートデータ
name 取得するエンティティの名前。kind に応じて意味が変わります。
  • kind が 'postType' の場合 → 投稿タイプのスラッグ(例: 'post', 'page' など)
  • kind が 'taxonomy' の場合 → タクソノミーのスラッグ(例: 'category', 'post_tag' など)
  • kind が 'root' の場合 → サイト全体に関連するエンティティ(例: 'user', 'taxonomy', 'theme' など)
カスタム投稿タイプやカスタムタクソノミーを使用する場合は、それらのスラッグを渡します。
query REST API に渡す抽出条件を指定するクエリパラメータのオブジェクト(オプション)。以下参照 例:{ per_page: 10, exclude:123 }

戻り値

エンティティレコードの配列 または null

基本的な構文

const records = select('core').getEntityRecords(kind, name, query);

query パラメータ

このオブジェクトは REST API のフィルタリングや検索機能を反映します。

getEntityRecords は WordPress REST API を使用してエンティティを作成しているので、抽出条件を指定する各パラメータは、WordPress REST API の各パラメータと共通しています。

例えば、getEntityRecords('postType', 'post') であれば REST API で /wp/v2/posts にアクセスするのと同様、Posts のパラメータに掲載されている以下のようなパラメータが使えます。

  • per_page: 取得するアイテム数
  • order: 並び順(例: 'asc', 'desc')
  • orderby: 並び替えの基準(例: 'title', 'date')
  • search: キーワード検索(マッチしたものを取得)
  • exclude: 除外する投稿 ID
  • categories:対象のカテゴリーの ID

アイキャッチ画像のデータも取得するには、_embed: true を指定します。

query パラメータを指定しない場合、デフォルトで REST API が提供する全データを取得しようとします。ただし、取得件数はサーバー設定(デフォルトは 10 件)に依存します。

投稿データを取得する例

以下は、postType が post(投稿)であるエンティティを取得する例です。

const posts = useSelect(
  (select) =>
    select("core").getEntityRecords("postType", "post", {
      per_page: 5, // 最大5件取得
      order: "asc", // 昇順
      orderby: "title", // タイトルでソート
      exclude: [123, select('core/editor').getCurrentPostId()], // IDが 123 と 現在の投稿を除外
    }),
  [],
);

// または以下でも同じ(パラメータを別途定義する場合の例)
const posts2 = useSelect((select) => {
  const query = {
    per_page: 5,
    order: "asc",
    orderby: "title",
    exclude: [123, select('core/editor').getCurrentPostId()],
  };
  return select("core").getEntityRecords("postType", "post", query);
}, []);

カテゴリーを取得する例

以下は、taxonomy が category であるエンティティを取得する例です。

const categories = useSelect((select) =>
  select("core").getEntityRecords("taxonomy", "category", {
    per_page: 10, // 最大10件取得
    order: "desc", // 降順
  }),
);

getEntityRecord

getEntityRecord は、WordPress REST API を通じて取得される 単一のエンティティ を取得するための関数です。

前述の getEntityRecords は複数のエンティティ(例: 投稿の一覧、カテゴリーの一覧)を取得するのに対し、getEntityRecord は単一のエンティティ(特定の投稿やタクソノミー項目)を取得します。

getEntityRecord(state, kind, name, key, query)

以下のパラメータを受け取ります。

state 状態ツリー(自動で処理されるので明示的に渡す必要はない。通常は省略)
kind エンティティの種類(例: 'postType')
name エンティティの名前(例: 'post')
key 特定のエンティティを識別するキー(通常は ID)
query リクエストをカスタマイズするためのオプション。例 { context: 'edit' } 。context を指定することで、データのスコープや内容を制御できます。context の一般的な値には以下の3つがあります。
  • view: 一般公開用のデータを取得
  • edit: 編集用のデータを取得(管理者や投稿編集者向け)
  • embed: 埋め込みや簡略表示用の軽量データを取得

戻り値

エンティティオブジェクト。値がまだ受信されていない場合は null を返し、値エンティティが存在しないことがわかっている場合は undefined を返し、エンティティが存在し受信されている場合はエンティティオブジェクトを返します。

基本的な構文

const record = select('core').getEntityRecord(kind, name, id);

特定の投稿を取得する例

以下は、ID が 123 の投稿データを取得する例です。

const post = useSelect((select) =>
  select("core").getEntityRecord("postType", "post", 123),
);

特定のカテゴリーを取得する例

以下は、ID が 123 のカテゴリーデータを取得する例です。

const cat = useSelect((select) =>
  select("core").getEntityRecord("taxonomy", "category", 123),
);

サイト情報を取得する例

以下はサイトのタイトルや URL、言語、日付フーマットなどのサイト情報を取得する例です。

const siteInfo = useSelect((select) =>
  select("core").getEntityRecord("root", "site"),
);

if (siteInfo) {
  console.log("Site Title:", siteInfo.title);
  console.log("Description:", siteInfo.description);
  console.log("Site URL:", siteInfo.url);
  console.log("Language:", siteInfo.language);
  console.log("Date Format:", siteInfo.date_format);
}

配列からデータをレンダリング

getEntityRecords を使って取得する投稿のデータはエンティティレコードの配列になっているので、各投稿のデータをレンダリングするには JavaScript の Array.prototype.map() メソッドを使います。

例えば、getEntityRecords で取得した投稿データの配列の各投稿のタイトルをレンダリングするには以下のように記述できます。

map() のコールバックでは、配列のそれぞれの要素(post)からタイトル( post.title.rendered )を取得して li 要素のコンテンツとして返しています。その際、各 li 要素には key 属性を指定します。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {
  const posts = useSelect(
    (select) =>
      select("core").getEntityRecords("postType", "post", {per_page: 5}),
    [],
  );

  // 投稿データ(posts)の配列を map() を使ってレンダリング
  return (
    <ul {...useBlockProps()}>
      {posts && posts.map((post) => (
        <li key={post.id}>{post.title.rendered}</li>
      ))}
    </ul>
  );
}

key 属性

map() 内で JSX 要素を使用する(React で配列をレンダリングする)場合、配列の各要素には key 属性を設定する必要があります。上記の例の場合、配列の各要素は li 要素になります。

key は兄弟間でその項目を一意に特定できるような文字列を指定します。この例の場合、投稿の ID は一意なので post.id を指定しています。

React ドキュメント:リストのレンダー

以下はエディターに各投稿のタイトルと抜粋をレンダリングする例です。抜粋(post.excerpt.rendered)は、HTML が含まれているため dangerouslySetInnerHTML を使用して実際の HTML としてレンダリングするようにしています。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {
  const posts = useSelect(
    (select) =>
      select("core").getEntityRecords("postType", "post", {
        per_page: 5,
        exclude: select('core/editor').getCurrentPostId() // 自身の投稿を除外
      }),
    [],
  );

  return (
    <div {...useBlockProps()}>
      {posts &&
        posts.map((post) => (
          <div key={post.id}>
            <h3>{post.title.rendered}</h3>
            <div dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
          </div>
        ))}
    </div>
  );
}

HTML としてレンダリング

以下は getEntityRecord で投稿を取得して、そのタイトルとコンテンツを表示するブロックの例です。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {
  const post = useSelect(
    // 投稿 ID が 123 の投稿データを取得
    (select) => select("core").getEntityRecord("postType", "post", 123),
    [],
  );

  // 投稿のタイトルとコンテンツを表示
  return (
    <div {...useBlockProps()}>
      {post && (
        <div>
          <h3>{post.title.rendered}</h3>
          <div>{post.content.rendered}</div>
        </div>
      )}
    </div>
  );
}

編集画面(エディター)を確認すると、例えば、以下のようにコンテンツのマークアップがテキストとして表示されます。

コンテンツ(content.rendered)や抜粋(excerpt.rendered)には HTML が含まれています。

タイトル(title.rendered)も HTML が含まれている可能性がありますが、WordPress 6.5 以降では、通常のビジュアルエディターモードでは HTML タグを記述できないようになっています(コードエディターに切り替えれば HTML を記述可能)。

コンテンツや抜粋などを実際の HTML としてレンダリングするには以下のような方法があります。

但し、どちらの方法もマークアップが完全に信頼できるソースから来ていない限り、DOM の innerHTML プロパティ同様、簡単に XSS 脆弱性が発生するので注意が必要です。

dangerouslySetInnerHTML

以下は dangerouslySetInnerHTML を使用してコンテンツを HTML としてレンダリングする例です。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {
  const post = useSelect(
    (select) => select("core").getEntityRecord("postType", "post", 123),
    [],
  );

  return (
    <div {...useBlockProps()}>
      {post && (
        <div>
          <h3>{post.title.rendered}</h3>
          <div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
        </div>
      )}
    </div>
  );
}

RawHTML

以下は RawHTML を使用してコンテンツを HTML としてレンダリングする例です。

RawHTML を利用するには、@wordpress/element からコンポーネントをインポートして、コンテンツを RawHTML 要素でラップします(RawHTML 要素でラップすると 対象の HTML は div 要素でラップされます)。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";
// RawHTML を @wordpress/element からインポート
import { RawHTML } from "@wordpress/element";

export default function Edit() {
  const post = useSelect(
    (select) => select("core").getEntityRecord("postType", "post", 123),
    [],
  );

  return (
    <div {...useBlockProps()}>
      {post && (
        <div>
          <h3>{post.title.rendered}</h3>
          <RawHTML>{post.content.rendered}</RawHTML>
        </div>
      )}
    </div>
  );
}
DOMPurify でサニタイズ

WordPress REST API は基本的にサニタイズ(適切なエスケープ処理)を行っていますが、カスタムプラグインやテーマが適切にサニタイズ処理を行っていない場合や、第三者からの入力を直接処理する場合は問題(リスク)が発生することがあります。

WordPress REST API のサニタイズ処理

  • 公開コンテンツ(context: 'view'):
    • REST API を通じて取得されるコンテンツは、デフォルトで XSS 攻撃を防ぐためにサニタイズされています。
    • 通常、wp_kses_post() 関数によって、許可されていない HTML タグや属性は除去されます。
  • 編集用コンテンツ(context: 'edit'):
    • 編集者向けのデータでは、未サニタイズの生データが返されることがあります。この場合、より多くの HTML タグや属性が含まれる可能性があり、危険性が高まります。

以下は DOMPurify というライブラリを使って追加でサニタイズする例です。

ブロックのディレクトリで以下を実行して DOMPurify を npm 経由でインストールします。

DOMPurify は HTML のサニタイズ(無害化)を行うライブラリで、フロントエンド(ブラウザ)でも動作するため(本番環境でも必要にるため)、dependencies(オプションなし)に追加します。

--save-prod は npm 5 以降ではデフォルトになので、指定しなくても dependencies に追加されます。

% npm install dompurify

DOMPurify をインポートして、DOMPurify.sanitize() を使ってコンテンツ(post.content.rendered)をサニタイズします。以下は dangerouslySetInnerHTML を使う場合の例です。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";
// DOMPurify をインポート
import DOMPurify from 'dompurify';

export default function Edit() {
  const post = useSelect(
    (select) => select("core").getEntityRecord("postType", "post", 123),
    [],
  );

  return (
    <div {...useBlockProps()}>
      {post && (
        <div>
          <h3>{post.title.rendered}</h3>
          <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(post.content.rendered)}} />
        </div>
      )}
    </div>
  );
}

以下は RawHTML を使う場合の例です。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";
// RawHTML を @wordpress/element からインポート
import { RawHTML } from "@wordpress/element";
// DOMPurify をインポート
import DOMPurify from 'dompurify';

export default function Edit() {
  const post = useSelect(
    (select) => select("core").getEntityRecord("postType", "post", 123),
    [],
  );

  return (
    <div {...useBlockProps()}>
      {post && (
        <div>
          <h3>{post.title.rendered}</h3>
          <RawHTML>{ DOMPurify.sanitize(post.content.rendered)}</RawHTML>
        </div>
      )}
    </div>
  );
}

テキストを取得してレンダリング

以下は getEntityRecord で投稿を取得して、そのタイトルと抜粋を表示するブロックの例です。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {
  const post = useSelect(
    // 投稿 ID が 234 の投稿データを取得
    (select) => select("core").getEntityRecord("postType", "post", 234),
    [],
  );

  // 投稿のタイトルと抜粋を表示
  return (
    <div {...useBlockProps()}>
      {post && (
        <div>
          <h3>{post.title.rendered}</h3>
          <div>{post.excerpt.rendered}</div>
        </div>
      )}
    </div>
  );
}

抜粋(excerpt.rendered)には HTML が含まれるので、 編集画面(エディター)を確認すると、例えば、以下のように抜粋の p 要素のマークアップがテキストとして表示されます。

また、タイトルもコードエディターを使って HTML を記述している場合、以下のようにそのマークアップがテキストとして表示されます。

以下は、抜粋とタイトルのテキストを取得してレンダリングする例です。

抜粋の場合では、HTML を格納するための要素 excerptElement を作成し、その HTML に innerHTML で post.excerpt.rendered から取得した抜粋のマークアップを設定します(22行目)。

そして excerptElement から textContent または innerText を使って抜粋のテキストを取得します。

excerptElement.textContent または excerptElement.innerText を取得することで、HTML タグが除去されたテキストのみが抽出されます(実際の HTML やスクリプトは評価・実行されない)。

タイトルも同様です。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {
  const post = useSelect(
    // 投稿 ID が 234 の投稿データを取得
    (select) => select("core").getEntityRecord("postType", "post", 234),
    [],
  );

  // 抜粋を格納する変数
  let excerpt = "";
  // タイトルを格納する変数
  let title = "";

  if (post) {
    // 抜粋の HTML を格納する要素 excerptElement を作成
    const excerptElement = document.createElement("div");
    // 抜粋の HTML を excerptElement の HTML に
    excerptElement.innerHTML = post.excerpt.rendered;;
    // 抜粋のテキストを excerptElement から取得
    excerpt = excerptElement.textContent || excerptElement.innerText || "";
    const titleElement = document.createElement("div");
    titleElement.innerHTML = post.title.rendered;;
    title = titleElement.textContent || titleElement.innerText || "";
  }

  // 投稿のタイトルと抜粋のテキストを取得してレンダリング
  return (
    <div {...useBlockProps()}>
      {post && (
        <div>
          {title && <h3>{title}</h3>}
          {excerpt && <div>{excerpt}</div>}
        </div>
      )}
    </div>
  );
}

但し、抜粋とタイトルのテキストを取得する処理は同じなので、以下のようにテキストを取得する関数を定義すれば、もっと簡潔に記述できます。

また、以下の関数 getTextContent では15行目の if (!htmlRendered) return "" により、null や undefined の場合に早期リターンすることでそれ以降の不要な実行を回避しています。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useSelect } from "@wordpress/data";

export default function Edit() {
  const post = useSelect(
    // 投稿 ID が 234 の投稿データを取得
    (select) => select("core").getEntityRecord("postType", "post", 234),
    [],
  );

  // HTML からテキストを取得する関数を定義
  const getTextContent = (htmlRendered) => {
    if (!htmlRendered) return "";
    const elem = document.createElement("div");
    elem.innerHTML = htmlRendered;
    return elem.textContent || elem.innerText || "";
  };

  // 投稿のタイトルと抜粋のテキストを取得してレンダリング
  return (
    <div {...useBlockProps()}>
      {post && (
        <div>
          {post.title.rendered && (
            <h3>{getTextContent(post.title.rendered)}</h3>
          )}
          {post.excerpt.rendered && (
            <p>{getTextContent(post.excerpt.rendered)}</p>
          )}
        </div>
      )}
    </div>
  );
}
DOMParser を使う

DOMParser は、文字列として与えられた HTML や XML を解析し、ブラウザの DOM オブジェクトとして扱えるようにするための Web API です。

これにより、サーバーから取得した HTML や XML を JavaScript 内で操作できるようになります。

以下は基本的な使い方です。

DOMParser.parseFromString() メソッドは、HTML または XML を含む文字列を解析し、HTMLDocument または XMLDocument を返します。

// DOMParser オブジェクトのインスタンスを作成
const parser = new DOMParser();
// parseFromString(HTML文字列, "text/html") を使って DOM に変換
const doc = parser.parseFromString("<p>Hello, <strong>world</strong>!</p>", "text/html");
// .body.textContent でテキストのみを取得(HTMLタグは削除される)
console.log(doc.body.textContent); // "Hello, world!"

前述の HTML からテキストを取得する関数 getTextContent は、以下のように DOMParser を使って書き換えることができます。

const getTextContent = (htmlRendered) => {
  // htmlRendered が null や undefined の場合、空文字 "" を返して処理を終了
  if (!htmlRendered) return "";
  // HTML を解析するための DOMParser インスタンスを作成
  const parser = new DOMParser();
  // HTML 文字列をパース(HTML 文字列を DOM に変換)
  const doc = parser.parseFromString(htmlRendered, "text/html");
  // doc.body.textContent を取得すると、HTML タグが削除され、テキストだけが得られる
  // 但し、textContent が null になる可能性があるため、|| "" でデフォルト値を設定
  return doc.body.textContent || "";
};

doc.body.textContent は、DOMParser を使って HTML をパースした後、body のすべてのテキストを取得するために使用されています。

doc.body は <body> 要素を指し、.textContent で、その中のテキスト部分だけを取得しています。

※ DOMParser は ブラウザの DOM API を使って HTML を解析するため、比較的コストがかかる処理です。そのため、前述の getTextContent のほうが軽量で、DOMParser を使うより処理が速いです。

小規模な投稿リスト(10件程度)なら、DOMParser でもユーザービリティに影響はほぼありませんが、大量の投稿を処理する場合は前述の getTextContent を使った方法のほうが速くなります。

DOMPurify を使う

もしサニタイズを徹底したい場合は、DOMPurify ライブラリを使うとより安全です。

DOMPurify がインストールされていない場合は、ブロックのディレクトリで以下を実行します。

% npm install dompurify

DOMPurify をインポートして、DOMPurify.sanitize() を使ってタグをすべて削除します。

import DOMPurify from "dompurify";

const getTextContent = (htmlRendered) => {
  const sanitizedHtml = DOMPurify.sanitize(htmlRendered, { ALLOWED_TAGS: [] }); // タグをすべて削除
  return sanitizedHtml.trim();
};

エディター内で通知(notice)を表示

useDispatch( 'core/notices' ) を使うと、createWarningNotice などの通知アクションを取得してエディター内で通知(notice)を表示することができます。

useDispatch

useDispatch は WordPress の @wordpress/data パッケージが提供する React フックで、指定したストア(データの管理単位)に対してアクションをディスパッチ(dispatch)するために使用されます。

useDispatch の基本構文は以下のようになります。

const { actionFunction } = useDispatch( storeName );
  • storeName: ディスパッチ対象のストアを指定(例:'core/notices')
  • actionFunction: ストアで提供されるアクションのオブジェクト(例:createWarningNotice)

core/notices ストアのアクション

useDispatch( 'core/notices' ) で取得できる主なアクションは以下のとおりです。

const {
  createNotice,
  createSuccessNotice,
  createErrorNotice,
  createWarningNotice,
  removeNotice
} = useDispatch( 'core/notices' );
メソッド 説明
createNotice( status, message, options ) 任意の種類の通知を作成
createSuccessNotice( message, options ) 成功通知を作成
createErrorNotice( message, options ) エラー通知を作成
createWarningNotice( message, options ) 警告通知を作成
removeNotice( id ) 指定した通知を削除

取得したアクションを使って例えば以下のように通知を作成することができます。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useDispatch } from "@wordpress/data";

export default function Edit() {
  const {
    createNotice,
    createSuccessNotice,
    createErrorNotice,
  } = useDispatch("core/notices");

  // 任意の種類の通知を作成 ( status, message, options )
  createNotice("success",
    "Operation completed successfully.",
    {
      type: "snackbar",
      isDismissible: true,
    }
  );

  // 成功通知 ( message, options )
  createSuccessNotice("Your changes have been saved.", { type: "snackbar" });

  // エラー通知 ( options を省略)
  createErrorNotice("Something went wrong.");

  return (
    <p {...useBlockProps()}>
      {__("My Dynamic Block – hello from the editor!", "my-dynamic-block")}
    </p>
  );
}

エディターを確認すると、例えば、以下のような通知が表示されます。

options で type: "snackbar" を指定した通知は左下に表示され、メッセージをクリックして消すことができますが、しばらくすると自動的に消えます。type を省略した場合は、画面上部に表示され、消すには X をクリックします。

また、この例の場合、createNotice などのアクションを Edit の関数本体内で直接呼び出しているので(React のコンポーネントはステートが更新されるたびに再レンダーされるため)、Edit が再レンダーされるたびにアクションも実行され、通知が複数回表示されています。

以下は showNotice というボタンがクリックされた際のコールバック関数を定義する例です。

ボタンがクリックされると、その際に一度だけ「You have clicked me!」と表示されます。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useDispatch } from "@wordpress/data";

export default function Edit() {
  const { createSuccessNotice } = useDispatch("core/notices");

  const showNotice = () => {
    createSuccessNotice( __( 'You have clicked me!' ), {
      type: 'snackbar',
    } );
  };

  return (
    <div {...useBlockProps()}>
      <button onClick={showNotice}>
            Click Me!
        </button>
    </div>
  );
}

一度だけ通知を表示する

以下は、useEffect を使い、コンポーネントのマウント時にのみ通知を表示する例です。

useEffect で空の依存配列を指定することで、初回のみ通知が実行されます。

import { __ } from "@wordpress/i18n";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";
import { useEffect } from '@wordpress/element'; // useEffect をインポート
import { useDispatch } from '@wordpress/data';

export default function Edit() {
  const { createNotice } = useDispatch( 'core/notices' );

    useEffect(() => {
      createNotice( 'success', 'Operation completed successfully.', {
        type: 'snackbar',
        isDismissible: true,
      });
    }, []);  // 空の依存配列にすることで、初回のみ実行

  return (
    <div {...useBlockProps()}>
      {/* 省略 */}
    </div>
  );
}

ストア名を指定する場合と、ストアをインポートする場合の違い

useDispatch() の引数にはストア名を指定することも、インポートしたストアを指定することもできます。

// ストア名を指定する場合
const { createWarningNotice } = useDispatch( 'core/notices' );

// インポートしたストアを指定する場合
import { store as noticeStore } from '@wordpress/notices';
const { createWarningNotice } = useDispatch( noticeStore );

ストア名を直接指定する方法は、 import が不要なのでシンプルです。インポートしたストアを指定する方法は、import により、ストアの型情報や関数の型補完が有効になります(TypeScript で便利)。

パフォーマンス的な違いはないので、開発チームのコーディングスタイルやプロジェクトの規模に応じて使い分けるのが良いようです。

dispatch と useDispatch

WordPress の @wordpress/data パッケージの dispatchuseDispatch は、状態管理(state management)において「ストアの状態を更新する」ために使います。

dispatch

dispatch は、指定したストア(store)に対してアクション(action)を送信し、状態を更新するための関数です。

以下は dispatch('core/editor') で core/editor ストアのアクションを取得し、.editPost({ title: '新しいタイトル' }) で editPost アクションを実行して、エディターの投稿データ title を更新します。

import { dispatch } from '@wordpress/data';

// core/editor ストアに対して、エディターのコンテンツを更新
dispatch('core/editor').editPost({ title: '新しいタイトル' });

非同期処理と組み合わせる

dispatch は Promise を返すため、.then() や await で処理を待つことができます。

async function updateTitle() {
  await dispatch('core/editor').editPost({ title: '非同期で更新されたタイトル' });
  console.log('タイトルが更新されました!');
}
updateTitle();
useDispatch

useDispatch は、React のカスタムフックで、特定のストアの dispatch を簡単に使えるようにするものです。React フックなので、React の関数コンポーネント内で使います。

useDispatch フックは dispatch をストアごとにバインドし(結びつけ)、より直感的に扱えるようにするので、より簡潔に記述できます。

以下は { editPost } = useDispatch('core/editor'); で core/editor ストアのアクションを分割代入で取得し、editPost アクションを直接使える形にしています。

ボタンをクリックすると editPost が実行され、タイトルが変更されます。

import { useDispatch } from '@wordpress/data';

const MyComponent = () => {
  // core/editor ストアのアクションを取得し、editPost アクションを分割代入
  const { editPost } = useDispatch('core/editor');

  return (
    <button onClick={() => editPost({ title: 'ボタンで更新' })}>
      タイトルを変更
    </button>
  );
};

非同期処理と組み合わせる

useDispatch も dispatch 同様に非同期で使えます。

const MyComponent = () => {
  const { editPost } = useDispatch("core/editor");

  const handleClick = async () => {
    await editPost({ title: "非同期更新" });
    console.log("タイトルが更新されました!");
  };

  return <button onClick={handleClick}>タイトル更新</button>;
};
どちらを使うべきか?

以下は dispatch と useDispatch の違いです。

dispatch useDispatch
種類 関数 React フック
使用場所 どこでも使える(グローバルな関数、イベント、クラスコンポーネントなど) React の 関数コンポーネント内のみ
使い方 dispatch('store').action() const { action } = useDispatch('store')
可読性 低め(ストア名を毎回書く必要がある) 高め(ストアのアクションを簡潔に記述できる)

dispatch を使うべきケース

  • React 以外の処理(例: 非同期処理、イベントリスナー)。
  • カスタムフックの外部やグローバルなユーティリティ関数内。

useDispatch を使うべきケース

  • React の関数コンポーネント内 で状態を更新する場合。
  • useDispatch を使うことでコードがシンプルになる場合。

パフォーマンスの最適化

React の useDispatch は React コンポーネントのライフサイクルに最適化されています。

例えば、一度 useDispatch を使って createWarningNotice を取得すると、コンポーネントの再レンダー時にも同じ関数を再利用できます。これは useMemo と同じような役割を果たし、不要な関数の再生成を防ぐ ため、パフォーマンスが向上します。

const { createWarningNotice } = useDispatch( 'core/notices' );

対して、dispatch を使った場合、関数が実行されるたびに dispatch( 'core/notices' ) が呼び出され、新しいオブジェクトが作成され、関数コンポーネントが頻繁に再レンダーされる場合、余分な処理が発生する可能性があります。

dispatch( 'core/notices' ).createWarningNotice(
  'Links are disabled in the editor.',
  { type: 'snackbar' }
);

ブロック開発では useDispatch を使う

WordPress のブロック開発では、関数コンポーネント内でのデータストア操作は useDispatch を使うのが一般的です。