1. ホーム
  2. WordPress

【WordPress】ブロックコンテキストの使い方|providesContext・usesContext で親の値を子ブロックに渡す

Share

親ブロックの中に子ブロックを並べる構成のカスタムブロックを作っていると、「親で選んだ色を子ブロックすべてに反映したい」「親で決めた通貨記号を子が使いたい」といった、親の設定を子に伝えたい場面が出てきます。子ブロックそれぞれに同じ設定項目を用意するのは現実的ではありません。こうしたときに使うのが、ブロックエディターのブロックコンテキストという仕組みです。この記事では、親側の providesContext と子側の usesContext の書き方、editrender.php での受け取り方、そしてコンテキストが届かないときの原因を解説します。WordPress 6 系、apiVersion 3 のブロックを前提としています。

属性は自分のブロックしか見られない

ブロックの属性(attributes)は、そのブロック自身が持つデータです。edit の props から受け取れるのは自分の属性だけで、親ブロックの属性を直接のぞくことはできません。エディターのストアを使えば clientId をたどって親の属性を取ることも一応できますが、階層をさかのぼる処理を自分で書くことになり、フロント側の表示では同じ手が使えません。

ブロックコンテキストは、この「親から子へ値を渡す」ためにブロックエディターが用意している正式な仕組みです。親が「この値を公開します」と宣言し、子が「この値を使います」と宣言すると、間に何段ブロックが挟まっていても子に値が届きます。React の Context API と考え方はほぼ同じで、props のバケツリレーをせずに値を共有できます。

宣言はどちらも block.json に書きます。役割は次のように分かれています。

項目書く側役割
providesContext親ブロック自分の属性を、名前を付けて子孫に公開する
usesContext子ブロック受け取りたいコンテキストの名前を並べる

親ブロックで providesContext を書く

例として、料金プランを並べる「プラン一覧」ブロック(親)と、個々の「プラン」ブロック(子)を作るとします。通貨記号と強調色は一覧側でまとめて決め、各プランはそれに従う、という設計です。

まず親の block.json です。公開したい値は属性として定義しておく必要がありますprovidesContext には「コンテキスト名」と「その値を持つ属性名」の対応を書きます。

plan-list/block.json(親)
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "myblocks/plan-list",
  "title": "プラン一覧",
  "category": "design",
  "attributes": {
    "currency": {
      "type": "string",
      "default": "円"
    },
    "accentColor": {
      "type": "string",
      "default": "#0073aa"
    }
  },
  "providesContext": {
    "myblocks/currency": "currency",
    "myblocks/accentColor": "accentColor"
  },
  "editorScript": "file:./index.js"
}

キーがコンテキスト名、値が属性名です。左右を逆に書いてしまいがちなので注意してください。"myblocks/currency": "currency" は「currency 属性の中身を myblocks/currency という名前で公開する」という意味です。

コンテキスト名には名前空間を付ける

コンテキスト名はサイト全体で共有される名前です。colorsize のような一般的な語をそのまま使うと、他のプラグインやコアのブロックと衝突する可能性があります。ブロック名と同じように 自分の接頭辞/キー名 の形にしておくのが安全です。

親側の edit は、いつものように属性を編集して InnerBlocks で子を受け入れるだけです。コンテキストのために特別な記述は要りません。

plan-list/edit.js(親)
import {
    InnerBlocks,
    InspectorControls,
    useBlockProps,
} from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {
    const { currency } = attributes;

    return (
        <>
            <InspectorControls>
                <PanelBody title="一覧の設定">
                    <TextControl
                        label="通貨記号"
                        value={ currency }
                        onChange={ ( value ) =>
                            setAttributes( { currency: value } )
                        }
                        __nextHasNoMarginBottom
                        __next40pxDefaultSize
                    />
                </PanelBody>
            </InspectorControls>
            <div { ...useBlockProps() }>
                <InnerBlocks allowedBlocks={ [ 'myblocks/plan' ] } />
            </div>
        </>
    );
}

属性を変更すると、その値がそのままコンテキストとして流れます。currency を「ドル」に変えれば、子ブロックの表示も即座に切り替わります。

子ブロックで usesContext を書いて受け取る

子側では usesContext に、受け取りたいコンテキスト名を配列で並べます。ここに書いていない名前は、親が公開していても edit には渡ってきません。必要なものだけを明示する形です。

plan/block.json(子)
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "myblocks/plan",
  "title": "プラン",
  "category": "design",
  "parent": [ "myblocks/plan-list" ],
  "attributes": {
    "planName": { "type": "string", "default": "" },
    "price": { "type": "number", "default": 0 }
  },
  "usesContext": [ "myblocks/currency", "myblocks/accentColor" ],
  "editorScript": "file:./index.js"
}

parent は「このブロックはこの親の中でしか挿入できない」という制限です。コンテキストの動作に必須ではありませんが、親の外に置かれて値が来ない状態を防げるので、対で使うと安心です。子の中にさらに孫を挟む構成なら、直近の親だけを見る parent ではなく、先祖のどこかにあればよい ancestor を使います。

受け取った値は edit の props の context に入っています。属性と同じように分割代入で取り出せますが、キーに / が入るのでプロパティ名をそのまま書けません。ブラケット記法か、名前を付け替える分割代入を使います。

plan/edit.js(子)
import { RichText, useBlockProps } from '@wordpress/block-editor';

export default function Edit( { attributes, setAttributes, context } ) {
    const { planName, price } = attributes;

    // キーに / が含まれるので名前を付け替えて受け取る
    const {
        'myblocks/currency': currency,
        'myblocks/accentColor': accentColor,
    } = context;

    return (
        <div { ...useBlockProps( { style: { borderColor: accentColor } } ) }>
            <RichText
                tagName="h3"
                value={ planName }
                onChange={ ( value ) => setAttributes( { planName: value } ) }
                placeholder="プラン名"
            />
            <p className="plan__price">
                { price.toLocaleString() }
                { currency }
            </p>
        </div>
    );
}

親の設定を変えると、中に並んだ子ブロックすべてが同時に描き変わります。子ブロック側に通貨記号の設定を持たせていないので、設定がばらつくこともありません。

save では context を使えない

ここが一番のつまずきどころです。save 関数にコンテキストは渡りません。 コンテキストはエディターの表示とサーバー側のレンダリングのための仕組みで、保存される HTML を作る save の段階では参照できません。save( { attributes, context } ) と書いても contextundefined です。

そのため、コンテキストの値をフロント側の表示に反映したい子ブロックは、動的ブロックにするのが基本の作りになりますblock.jsonrender を指定し、save は書かない(または InnerBlocks.Content だけを返す)形にします。

plan/block.json(動的ブロックにする)
{
  "usesContext": [ "myblocks/currency", "myblocks/accentColor" ],
  "editorScript": "file:./index.js",
  "render": "file:./render.php"
}

render.php の中では $block という変数が使えて、そのプロパティ context にコンテキストの値が入っています。ここでも usesContext に書いた名前だけが渡ってきます。

plan/render.php
<?php
/**
 * $attributes … ブロックの属性
 * $content    … save が返した内容
 * $block      … WP_Block オブジェクト(context を持つ)
 */
$currency = $block->context['myblocks/currency'] ?? '円';
$accent   = $block->context['myblocks/accentColor'] ?? '#0073aa';

$price = isset( $attributes['price'] ) ? (int) $attributes['price'] : 0;
?>
<div <?php echo get_block_wrapper_attributes(
    array( 'style' => 'border-color:' . esc_attr( $accent ) )
); ?>>
    <h3><?php echo esc_html( $attributes['planName'] ?? '' ); ?></h3>
    <p class="plan__price">
        <?php echo esc_html( number_format( $price ) . $currency ); ?>
    </p>
</div>

親のほうも、子を包む枠を出力する必要があるので saveInnerBlocks.Content を返すか、同じく動的ブロックにします。親を静的ブロックにしても、子が動的であればコンテキストは正しく届きます。

どうしても静的ブロックのまま保存したい場合は、コンテキストではなく親から子の属性を書き換える方向で考えます。親の editcore/block-editor ストアの updateBlockAttributes を使い、子の属性に値をコピーしておけば save でも参照できます。ただし値の二重管理になるので、まずは動的ブロックを検討するほうが素直です。

コアのブロックが公開しているコンテキストを使う

コンテキストは自作ブロック同士だけのものではありません。コアのブロックもこの仕組みで値を渡し合っています。代表的なのがクエリーループの中で使われるものです。

コンテキスト名公開しているブロック中身
postIdcore/post-template繰り返し中の投稿のID
postTypecore/post-template投稿タイプのスラッグ
queryIdcore/queryクエリーループの識別子

つまり、自作ブロックの usesContextpostIdpostType を書いておけば、クエリーループの中に置いたときに「いまループしている投稿」が分かります。投稿ごとに独自の情報を出すブロックを作るときに便利な使い方です。

クエリーループの中で投稿IDを受け取る
{
  "name": "myblocks/reading-time",
  "usesContext": [ "postId", "postType" ],
  "render": "file:./render.php"
}

コアが公開している名前には名前空間が付いていません。自作のコンテキストと見分けがつくよう、自分のものには必ず接頭辞を付けておきましょう。

子ブロックに値が届かない原因

usesContext に書き忘れている

親で公開していても、子が宣言していなければ渡りません。context が空のオブジェクトになっていたら、まず子の block.json を確認します。名前は完全一致で照合されるため、myblocks/accentColormyblocks/accentcolor と書いた程度のずれでも届かなくなります。

block.json の変更がビルドに反映されていない

usesContextprovidesContext はブロックの登録情報なので、block.json を書き換えたらビルド結果の block.json も更新される必要があります。wp-scripts start を動かしっぱなしにしていても、JSON の変更が反映されないことがあります。値が来ないときは npm run build を実行し直し、エディターを再読み込みしてから確認してください。

入れ子の親子関係になっていない

コンテキストはブロックの入れ子構造をたどって流れます。エディター上で隣に並んでいるだけのブロックには届きません。親の InnerBlocks(またはブロックテンプレート)の内側に子が入っているかを、リスト表示で確認するのが確実です。グループブロックなどを間に挟んでも、入れ子になっていれば下まで流れていきます。

属性として定義していない値を公開しようとしている

providesContext が参照できるのは、親の attributes に定義された属性だけです。計算した値やその場の変数を渡すことはできません。公開したい値は必ず属性として持たせ、その属性名を providesContext に書きます。属性のデフォルト値を決めておけば、親が未設定のときも子が undefined を受け取らずに済みます。

まとめ

ブロックコンテキストは、親ブロックの属性を子孫ブロックに渡すための仕組みです。親の block.jsonprovidesContext で「コンテキスト名: 属性名」を書き、子の block.jsonusesContext で受け取る名前を並べるだけで、間に何段挟まっていても値が届きます。名前は他と衝突しないよう 接頭辞/キー名 の形にしておきましょう。

受け取り側は、エディターでは edit の props の context、フロント側では render.php$block->context です。save には渡らないので、コンテキストの値を表示に使う子ブロックは動的ブロックとして作ります。ここを知らないまま save で使おうとして詰まるケースが多いところです。

コアの postIdpostType を受け取れば、クエリーループの中で投稿ごとに動くブロックも作れます。親子構成のブロックを作るなら、まず何をコンテキストにして何を各ブロックの属性にするかを整理してから書き始めると、設定項目の重複がない見通しのよい作りになります。

参考ページ