2026年6月16日

2026年6月16日

WordPressのブロックエディターストアをuseSelect/useDispatchで操作する実装方法

ブロックエディターの中身(どのブロックが選ばれているか、何個あるか、属性は何か)を読むには @wordpress/data の useSelect を、ブロックの追加・更新・削除を行うには useDispatch を、ブロックエディター用のストア blockEditorStore(ストア名 core/block-editor)に対して使います。この記事では、エディター用スクリプトの読み込み方から、選択中のブロックの情報を読む、ブロックを挿入する、属性を書き換える、という基本の3操作を動くコードで説明し、再描画が止まらない・値が更新されないといった、つまずきやすい点も整理します。

ブロックエディターのストアとは

ブロックエディターの状態は、@wordpress/data というReduxに近い仕組みの「ストア」に集約されています。ブロックの並び、選択状態、属性はストアに保存されていて、エディターの各部品はそこから読み、そこへ変更を依頼します。自作の部品も同じ経路を使えば、標準の機能と矛盾せずにエディターを操作できます。

よく使うストアは次の3つです。

  • blockEditorStore(@wordpress/block-editor、名前は core/block-editor): ブロックの並び・選択・属性など、編集中のブロックそのもの
  • editorStore(@wordpress/editor、名前は core/editor): 編集中の投稿のタイトル、ステータス、保存処理など、投稿単位の情報
  • coreStore(@wordpress/core-data、名前は core): REST API経由で取得する投稿・ユーザー・設定などのデータ

この記事で扱うのは1つ目です。ストアは文字列の名前でも指定できますが、現在はパッケージから読み込んだストアオブジェクトで指定する書き方が推奨されています。書き間違いがビルド時に分かり、依存関係も自動で解決されるためです。

準備:エディター用スクリプトを読み込む

import 文を使うので、@wordpress/scripts でビルドする構成を前提にします。プラグインのフォルダーで npm install @wordpress/scripts --save-dev を実行し、src/index.js を書いて npx wp-scripts build を実行すると、build/index.js と、依存スクリプトの一覧を書き出した build/index.asset.php が作られます。

PHP側では enqueue_block_editor_assets フックで、ブロックエディターの画面にだけスクリプトを読み込みます。index.asset.php の依存一覧をそのまま渡すと、wp-data や wp-block-editor などのコアのスクリプトが先に読み込まれます。

<?php
/**
 * Plugin Name: My Block Editor Tools
 */

add_action( 'enqueue_block_editor_assets', function () {
    $asset_file = plugin_dir_path( __FILE__ ) . 'build/index.asset.php';
    if ( ! file_exists( $asset_file ) ) {
        return; // ビルド前は何もしない
    }
    $asset = include $asset_file;

    wp_enqueue_script(
        'my-block-editor-tools',
        plugins_url( 'build/index.js', __FILE__ ),
        $asset['dependencies'],
        $asset['version'],
        true
    );
} );

テーマの functions.php に書くこともできますが、テーマを切り替えると機能が消えるため、エディターを拡張するコードは小さなプラグインにまとめるのが扱いやすい方法です。テーマに書く場合は、更新で上書きされないよう子テーマに置いてください。

useSelectで選択中のブロックを読む

useSelect には、ストアから値を取り出す関数と、依存配列を渡します。ストアの状態が変わるたびに関数が実行され、戻り値が前回と変わったときだけ部品が再描画されます。

次の例は、文書設定のサイドバーにパネルを追加し、選択中のブロックの名前と、全体のブロック数を表示します。

import { registerPlugin } from '@wordpress/plugins';
import { PluginDocumentSettingPanel } from '@wordpress/editor';
import { useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

function BlockInfoPanel() {
    const { blockName, blockCount } = useSelect( ( select ) => {
        const { getSelectedBlock, getBlockCount } = select( blockEditorStore );
        const block = getSelectedBlock();
        return {
            blockName: block ? block.name : null,
            blockCount: getBlockCount(),
        };
    }, [] );

    return (
        <PluginDocumentSettingPanel name="my-block-info" title="ブロック情報">
            <p>選択中: { blockName ?? 'なし' }</p>
            <p>トップレベルのブロック数: { blockCount }</p>
        </PluginDocumentSettingPanel>
    );
}

registerPlugin( 'my-block-info', { render: BlockInfoPanel } );

PluginDocumentSettingPanel は、WordPress 6.6 からは @wordpress/editor から読み込む形になりました。それより古い環境では @wordpress/edit-post から読み込みます(2026年10月現在)。読み込み元は更新で変わることがあるため、開発者向けドキュメントで確認してください。

getBlockCount() は引数を省略するとトップレベルのブロックだけを数えます。グループやカラムの中まで数えたい場合は、getBlocks() が返す配列の innerBlocks を再帰的にたどります。

よく使うセレクターは次のとおりです。

  • getSelectedBlockClientId(): 選択中のブロックのID(clientId)
  • getSelectedBlock(): 選択中のブロックのオブジェクト(name、attributes、innerBlocks を持つ)
  • getBlocks( rootClientId ): ブロックの配列。引数を省略するとトップレベル
  • getBlock( clientId ) / getBlockAttributes( clientId ): 指定したブロックとその属性
  • getBlockRootClientId( clientId ) / getBlockIndex( clientId ): 親ブロックのIDと、親の中での位置

clientId は編集画面の中だけの一時的なIDで、読み込み直すと変わるため、保存して後で使う用途には向きません。

useDispatchでブロックを挿入する

useDispatch( blockEditorStore ) は、ストアに変更を依頼する関数(アクション)の一覧を返します。新しいブロックは @wordpress/blocks の createBlock で作り、insertBlock で挿入します。

次の部品は、選択中のブロックの直後に段落ブロックを1つ追加します。何も選ばれていなければ末尾に追加します。

import { useSelect, useDispatch } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { createBlock } from '@wordpress/blocks';
import { Button } from '@wordpress/components';

function InsertNoteButton() {
    const { clientId, rootClientId, index } = useSelect( ( select ) => {
        const be = select( blockEditorStore );
        const id = be.getSelectedBlockClientId();
        return {
            clientId: id,
            rootClientId: id ? be.getBlockRootClientId( id ) : undefined,
            index: id ? be.getBlockIndex( id ) : -1,
        };
    }, [] );

    const { insertBlock } = useDispatch( blockEditorStore );

    const onClick = () => {
        const block = createBlock( 'core/paragraph', {
            content: 'ここに補足を書きます。',
        } );
        if ( clientId ) {
            // 選択中のブロックと同じ親の中で、すぐ後ろに入れる
            insertBlock( block, index + 1, rootClientId || undefined );
        } else {
            insertBlock( block ); // 位置を省略すると末尾に追加
        }
    };

    return (
        <Button variant="secondary" onClick={ onClick }>
            補足の段落を追加
        </Button>
    );
}

挿入位置は「親ブロックのIDと、その中での順番」の組で指定します。親を指定しないと、カラムの中のブロックを選んでいてもトップレベルに挿入されてしまうため、getBlockRootClientId で親を取得して渡しています。

挿入以外では、removeBlock( clientId )(削除)、replaceBlock( clientId, block )(置き換え)、moveBlockToPosition(移動)、selectBlock( clientId )(選択)がよく使われます。

属性を書き換える:updateBlockAttributes

ほかのブロックの属性を変えるには updateBlockAttributes( clientId, 属性 ) を使います。渡したキーだけが上書きされ、ほかの属性はそのまま残ります。次の例は、投稿内の画像ブロックのうち、代替テキストが空のものを一覧にし、まとめて同じ代替テキストを入れます。

import { useSelect, useDispatch } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';

// ブロックの木構造をたどって、画像ブロックだけを集める
function collectImages( blocks, found = [] ) {
    blocks.forEach( ( block ) => {
        if ( block.name === 'core/image' ) {
            found.push( block );
        }
        collectImages( block.innerBlocks, found );
    } );
    return found;
}

function FillEmptyAlt() {
    const blocks = useSelect(
        ( select ) => select( blockEditorStore ).getBlocks(),
        []
    );
    const { updateBlockAttributes } = useDispatch( blockEditorStore );

    const targets = collectImages( blocks ).filter(
        ( block ) => ! block.attributes.alt
    );

    const onClick = () => {
        targets.forEach( ( block ) => {
            updateBlockAttributes( block.clientId, { alt: '記事内の画像' } );
        } );
    };

    return (
        <Button variant="secondary" onClick={ onClick } disabled={ ! targets.length }>
            代替テキストが空の画像 { targets.length } 件に入力
        </Button>
    );
}

useSelect の中では getBlocks() の戻り値をそのまま返し、絞り込みは外で行っています。理由は次の節で説明します。なお、自作ブロックの edit 関数の中で、そのブロック自身の属性を変える場合は、ストアを使わず setAttributes を使うのが基本です。ストアを使うのは、ほかのブロックや、子ブロックを操作したいときです。

つまずきやすい点

useSelectの中で新しい配列やオブジェクトを作る

useSelect の関数の中で filter や map を使うと、状態が変わっていなくても毎回新しい配列が返り、「前回と違う値」と判定されて再描画が繰り返されます。開発用のビルドでは、同じ状態なのに異なる値が返っている、という趣旨の警告がブラウザのコンソールに出ます。ストアから取り出した値はそのまま返し、加工は外で行うか、useMemo で結果を使い回します。数値や文字列だけを持つオブジェクトを返すのは問題ありません。比較は中身の値で行われるためです。

依存配列を書かない、または足りない

関数の中で部品の外の値(props など)を使う場合、それを依存配列に入れないと、値が変わっても古い値のまま読み続けます。たとえば ( select ) => select( blockEditorStore ).getBlock( clientId ) と書くなら、依存配列は [ clientId ] です。

useSelectの中でアクションを呼ぶ

useSelect の関数は描画のたびに何度も呼ばれるので、そこで updateBlockAttributes などを呼ぶと変更と再描画の無限ループになります。変更はクリックなどのイベント処理か、useEffect の中で行います。

名前が「__experimental」や「__unstable」で始まるもの

ストアには、名前が __experimental や __unstable で始まるセレクターやアクションも含まれています。これらはWordPressの更新で予告なく変わったり消えたりすることがあるため、本番のプラグインでは使わないほうが安全です。

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

コードを書く前に、エディターを開いた状態でブラウザの開発者ツールのコンソールに wp.data.select( 'core/block-editor' ).getBlocks() と入力すると、今の状態をその場で確認できます。セレクターの戻り値の形を確かめてから部品に組み込むと、手戻りが減ります。ただし、コンソールからアクションを実行すると投稿の内容が実際に変わるので、テスト用の投稿で試してください。

まとめ

  • ブロックの並び・選択・属性は blockEditorStore に集まっており、読むときは useSelect、変えるときは useDispatch を使う
  • スクリプトは enqueue_block_editor_assets で読み込み、index.asset.php の依存一覧を渡す
  • 挿入は createBlock と insertBlock、属性の更新は updateBlockAttributes。挿入位置は親のIDと順番で指定する
  • useSelect の中では値を加工せずに返し、依存配列を正しく書き、アクションは呼ばない
  • __experimental などの不安定なAPIは避け、コンソールで戻り値を確認してから組み込む

お気軽にご相談ください

お見積りへ お問い合わせへ
▲