WordPressのブロック開発メモ その8 フック(アクションとフィルター)

前回は高階コンポーネントについて解説しました。

WordPressのブロック開発メモ その7 HOC(高階コンポーネント)

今回はフックについてです。

ブロックにもワードプレス本体のフックのようなものがあります。
これは特に説明するような内容では無いのでドキュメント読んでください。

フックはイベントのような物で、何かの処理が行われたさいに処理するコールバックを登録します。

フックはフィルターとアクションがあります。
アクションとフィルターの違いをラムダ式、アロー関数で表現すると、

アクション

(data) => 何かする

フィルター

(data) => dataを編集してdataを返す

の違いがあります。フィルターは戻り値を返します。

全てを把握するのは大変なのでいくつか触ってみます。

フックをindex.tsに追加します。

Block側のフック

blocks.registerBlockType

import { addFilter } from '@wordpress/hooks';

addFilter(
    'blocks.registerBlockType',
    'kurage-hooks/test-1',
    (settings, name: string) =>
    {
        if(name === 'create-block/kurage-worker')
        {
            console.log(settings);

        }

        return settings;
    }
);

コンソールを見ると見おぼえあるプロパティが!
block.jsonの設定を使用する前に上書することが出来ます。

フィルタを登録するにはaddFilter()を使います。

第一引数にフック名を指定します。
設定情報を上書するにはblocks.registerBlockTypeを指定します。
このフィルタはregisterBlockType()でブロックの追加したときに呼び出されます。

第二引数はフックの名前空間(ご自由に)。

第三引数でコールバックを指定します。
コールバックから渡される引数はフック毎に異なります。

今回はsettingsで設定の内容を、nameはブロックの名前が渡されます。

注意しないといけないのは、登録されているコアのブロックも受け取ってしまうことです。
結構な量のコアブロックが登録してあるので、ブロックの名前(name)で目的のブロックを判断します。

settingsを上書する必要があれば上書します。
必ず戻り値を返します。

blocks.getSaveElement

saveを上書します。

index.tsx

addFilter(
    'blocks.getSaveElement',
    'create-block/kurage-save',
    (element, blockType, attributes) =>
    {
        if(blockType.name === 'create-block/kurage-worker')
        {
            if(element)
            {
                return (<div className="kurage-wrap">{element}</div>)
            }
        }

        return element;
    }
)

save.tsx

export default (props: BlockSaveProps<KurageExampleBlockProps>) =>
{
    return (
        <p { ...useBlockProps.save() }>
            Hello Kurage
        </p>
    );
}

style.scss

.kurage-wrap
{
    border: 3px dotted green;
    padding: 1rem;
}

編集ページの画面

プレビューでの表示

フィルタの適用をKurage Workerブロックに限定してます。

if(blockType.name === 'create-block/kurage-worker')

動作を確認するため編集ページでKurage Workerブロック意外にカスタムHTMLブロックと画像ブロックも追加します。

プレビューを開くとKurage Workerブロックだけに限定されているのが確認出来ます。

もちろんifを使わず全てのブロックに適用することも出来ます。
保存するとカスタムHTMLブロックや画像ブロックにまで適用されてます。

editor.BlockEdit と editor.BlockListBlock

BlockListBlockBlockEditをフィルタリングします。
このBlockListBlock、ちょーがつくほど重要で、後で深堀していきます。
まずは次の例を見てみましょう。

style.scss

.kurage-block
{
    border: 8px dotted green;
    padding: 1rem;
    margin: 5rem;
}

.kurage-list
{
    border: 8px dotted red;
    padding: 1rem;
}

.kurage-content
{
    border: 8px dotted rgb(255, 174, 0);
    padding: 1rem;
}

index.tsx

addFilter(
    'editor.BlockEdit',
    'create-block/kurage-be',
    createHigherOrderComponent((BlockEdit) =>
    {
        return props => (
            <div className="kurage-block">
                <p>BlockEdit Begin</p>
                <BlockEdit {...props}  />
                <p>BlockEdit End</p>
            </div>
        )
    }, 'MyBlockEdit')
)

addFilter(
    'editor.BlockListBlock',
    'create-block/kurage-blb',
    createHigherOrderComponent((BlockListBlock) =>
    {
        return props => (
            <>
                <div className='kurage-list'>
                    <p>BlockListBlock Begin</p>
                    <BlockListBlock {...props} className="kurage-content" />
                    <p>BlockListBlock End</p>
                </div>
            </>
        )
    }, 'MyBlockListBlock')
);

何故かBlockListBlockで指定したclassNameedit側に適用されます。
何故かというか、そういう設計がされてます。

フィルタから受け取る引数のBlockListBlockがブロック本体で、BlockEditが編集コンポーネント(edit)です。

wrapperPropsを使うと

filter

addFilter(
    'editor.BlockEdit',
    'create-block/kurage-be',
    createHigherOrderComponent((BlockEdit) =>
    {
        return props => (
            <div className="kurage-block">
                <p>BlockEdit Begin</p>
                <BlockEdit {...props}  />
                <p>BlockEdit End</p>
            </div>
        )
    }, 'MyBlockEdit')
)

addFilter(
    'editor.BlockListBlock',
    'create-block/kurage-blb',
    createHigherOrderComponent((BlockListBlock) =>
    {
        return props => (
            <>
                <div className='kurage-list'>
                    <p>BlockListBlock Begin</p>
                    <BlockListBlock {...props} className="kurage-content" wrapperProps={{'data-message': 'Kurage Attack!'}} />
                    <p>BlockListBlock End</p>
                </div>
            </>
        )
    }, 'MyBlockListBlock')
);

BlockListBlockwrapperPropsdata-messageプロパティを追加。

edit

export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
    const cc = useBlockProps();

    return (
        <div {...useBlockProps()}>

            <div style={{whiteSpace: 'pre'}}>
                { JSON.stringify(cc, null, "\t") }
            </div>

            Hello Kurage

        </div>
    );
}

wrapperPropsで設定したプロパティは編集コンポーネント側のuseBlockProps()で取得出来ます。

data-messageKurage Attack!になっていることが確認出来ます。
またDOMにも反映していることが確認出来ます。

これまで何となく使ってきたuseBlockProps()は実はBlockListBlockで設定されたコンテクストでもあります(後で説明)。

フックの追加時、削除時に呼び出されるアクション

addAction()を使い、フックの追加と削除を知ることが出来ます。
ただし、呼び出す順番によって内容が変わるので注意してください。

import { addAction, addFilter, removeFilter } from '@wordpress/hooks';

addAction('hookRemoved', 'kurage/hookRemoved', fn => {
    console.log(` > ${fn} が削除されました`);
});

addAction('hookAdded', 'kurage/hookAdded', fn => {
    console.log(` > ${fn} が追加されました`);
});

addFilter('MyHooks', 'kurage/my-hooks', v => {});
removeFilter('MyHooks', 'kurage/my-hooks');
MyHooks が追加されました
MyHooks が削除されました
editor.BlockEdit が追加されました
editor.BlockListBlock が追加されました
blocks.switchToBlockType.transformedBlock が追加されました
blocks.switchToBlockType.transformedBlock が追加されました
blocks.registerBlockType が追加されました
editor.Autocomplete.completers が追加されました
editor.MediaUpload が追加されました
editor.BlockEdit が追加されました
blocks.registerBlockType が追加されました
blocks.registerBlockType が追加されました
editor.BlockEdit が追加されました
blocks.registerBlockType が追加されました
blockEditor.__unstableCanInsertBlockType が追加されました
blocks.registerBlockType が追加されました
blockEditor.__unstableCanInsertBlockType が追加されました
plugins.pluginRegistered が追加されました
plugins.pluginUnregistered が追加されました
heartbeat.send が追加されました
heartbeat.tick が追加されました

コアで登録されたフックもまとめて検出されてます。
ただしhookAddedを登録する前のフックの追加は検出出来ていません。

ログのうち今回登録/削除したMyHooksの部分を見るとちゃんと機能していることが確認できます。

MyHooks が追加されました
MyHooks が削除されました

きちんとフックを検出出来てます。

アクションとフィルタ

さて、アクション、フィルタ、これらはPHP側にもあるのでわざわざ説明しなくてもいいと思いますが。
一応試してみましょう。

アクション

アクションはイベントのような物です。

import { addAction, addFilter, doAction } from '@wordpress/hooks';

addAction('Fired', 'kurage/fire-1', (...args) => console.log(`fire-1: ${args.join(', ')}`))
addAction('Fired', 'kurage/fire-2', (...args) => console.log(`fire-2: ${args.join(', ')}`))

doAction('Fired', 'Hello', 'World');
fire-1: Hello, World
fire-2: Hello, World

addAction()によってFiredアクションを二つ登録しています。
どちらも引数をまとめてコンソールに出力するものです。

applyFilter()でアクションを実行します。
Firedフック名で登録されたコールバックを呼び出します。
その際、HelloWorld二つの引数を渡してます。

フィルタ

フィルタはcompose()のようなものです。

import { addFilter, applyFilters } from '@wordpress/hooks';

addFilter('MyValue', 'kurage/plusplus', value => value + 1);
addFilter('MyValue', 'kurage/pow', value => value * value);

const val = applyFilters('MyValue', 3);
console.log(val);
16

最初に登録したフィルタはkurage/plusplusで値を1足して返すフィルタです。
次に登録したフィルタはkurage/powで値を二乗にして返すフィルタです。
フィルタは値を必ず返します。

applyFilter()MyValueの名前で登録されたフィルタに3を渡してます。
3に1足して二乗した値をapplyFilter()は戻り値として返します。
3に1を足して4、さらに二乗した16が戻り値です。

PHP側のフック

ついでにPHP側のフィルタの一部を見てみます。

block_type_metadata

以前kurage-worker.phpでブロックを登録しました。

register_block_type( __DIR__ . '/build' );

このブロック登録の関数は引数がブロックのJSONファイルであればそれを読み込みます。
その読み込んだデータに変更を加えることが出来ます。

add_filter('block_type_metadata', function($metadata){

    if($metadata['name'] === 'create-block/kurage-worker')
    {
        sleep(1);
    }
    return $metadata;
});

ブレークポイントで止めています。
コアのブロック含め片っ端から検出してしまうので自分で作ったブロックの名前の時だけスリープするようにしてます。

block_type_metadata_settings

ちょっと違いが分かりにくいが、register_block_type()って次のような使い方が出来る。

register_block_type(
    'kurage/example-block',
    [
        'editor_script' => 'my-block',
        'style' => 'my-block-style',
        'editor_style' => 'my-block-editor-style'
    ]
);

ブロックを登録する関数で、引数にそのブロックのデータを渡すのだけど、
第一引数がJOSNだった場合はそのデータが読み込まれ、第二引数とマージされる。

このJSONだった時、JSONのプロパティ名とPHPのプロパティ名では命名規則が異なったり差異が存在する。
そこでJSONから読み込まれたデータはPHPの命名規則に変換する処理が行われる。
もちろんそれ以外にもいろいろな事情で変換処理が行われるんだろうけども。

最終的なPHP命名規則のデータはWP_Block_Typeのコンストラクタに渡される。

試しに$settings, $metadataのキーにアンダースコアか大文字が入っている名前を列挙してみた。

$aaaaa = [];

add_filter('block_type_metadata_settings', function($settings, $metadata){

    static $s = [];
    static $m = [];

    $skeys = array_keys($settings);
    $mkeys = array_keys($metadata);

    $check = function($key)
    {
        return strpos($key, '_') !== false || preg_match('/[A-Z]/', $key);
    };

    $s = array_unique(array_merge($s, array_filter($skeys, fn($key) => $check($key))));
    $m = array_unique(array_merge($m, array_filter($mkeys, fn($key) => $check($key))));

    global $aaaaa;
    $aaaaa = [$s, $m];

    return $metadata;
}, 10, 2);

add_action('wp_loaded', function(){

    global $aaaaa;

    $x = 4;

});

BlockListBlockを深堀していく。

ここからはかなり深い内容です。
普通にブロック開発するうえで基本的に必要ありません。
どうしてもBlockListBlockの正体が気になる人だけお付き合いいただければ。

ブロックの階層を知る

editor.BlockListBlockeditor.BlockEditはどう違う?
これを理解するためにグーテンベルグのコードを追っていきました。

  • 実際に試したわけではなくコードを目で追っただけなので間違っている可能性があります。

編集ページの階層はどうなってるのか気になって調べてみました。
実際にはコンテクスト類がごちゃごちゃ入ってますが大雑把に省いたらこんな感じになってました。

<App>
    <Layout>
        <Editor>
            <BlockEditor>
                <BlockList>
                    <BlockListBlock>
                        <BlockEdit>
                            <Edit />
                        </BlockEdit>
                    </BlockListBlock>
                </BlockList>
            </BlockEditor>
        </Editor>
    </Layout>
</App>

この階層を把握するのはとても重要です。

editor.BlockListBlockBlockListBlockで、
editor.BlockEditEdit(BlockEditではない)でフィルタが使用されます。

それぞれのコンポーネントのソースコードです。
この記事を書いている時点のもので「行」などの変更がある可能性もあります。
その点には注意してください。

@wordpress/edit-site

initializeEditor()

https://github.dev/WordPress/gutenberg/blob/trunk/packages/edit-site/src/index.js#L92

App

https://github.dev/WordPress/gutenberg/blob/trunk/packages/edit-site/src/components/app/index.js#L39

Layout

https://github.dev/WordPress/gutenberg/blob/trunk/packages/edit-site/src/components/layout/index.js#L328

Editor

https://github.dev/WordPress/gutenberg/blob/trunk/packages/edit-site/src/components/editor/index.js#L175

BlockEditor

https://github.dev/WordPress/gutenberg/blob/trunk/packages/edit-site/src/components/block-editor/index.js#L204

@wordpress/block-editor

// BlockListBlockはBlockListItemsコンポーネントを呼び出すことによってレンダリングされるが、そのコンポーネントはuseInnerBlockProps()から取得される。

BlockList

https://github.dev/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/block-list/index.js#L103

https://github.dev/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/block-list/index.js#L175

useInnerBlocksProps()

https://github.dev/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/inner-blocks/index.js#L113

BlockListBlock

https://github.dev/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/block-list/block.js#L548

https://github.dev/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/block-list/block.js#L260

BlockEdit

https://github.dev/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/block-edit/index.js#L51

Edit

https://github.dev/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/block-edit/edit.js#L72

BlockListBlockがブロックの本体のような感じで、
例えば検証エラーの時はそれを表示したり、HTMLとして編集できるようにしたり、配置(data-align)を実装したり一通りの実装をしている。
まだ紹介してないがwithSelect()withDispatch()が適用されているところでもある。

ソースコードを見ると次のように定義してある。

// BlockListBlockの本体はめっちゃ長いコードなので省略。
function BlockListBlock(...) ...

// compose()でeditor.BlockListBlockが適用されている
export default compose(
    pure,
    applyWithSelect,
    applyWithDispatch,
    // Block is sometimes not mounted at the right time, causing it be undefined
    // see issue for more info
    // https://github.com/WordPress/gutenberg/issues/17013
    ifCondition( ( { block } ) => !! block ),
    withFilters( 'editor.BlockListBlock' )
)( BlockListBlock );

次にEdit
ブロック登録の際の設定情報のsaveeditをレンダリングする場所でもある。

Editが行ってることをいろいろ省いて大雑把に書いてみるとこんな感じになる。

// 本体のソースコードが若干複雑なので簡素化してます。
export const Edit = props => {

     // registerBlockType(name, ...)で設定した情報を取得
    const blockType = getBlockType(props.name);

    // edit or save を取得
    const Component = blockType.edit || blockType.save;

    // edit or save をレンダリング
    return <Component ... />
}

// compose()でeditor.BlockEditが適用されている
export default withFilters( 'editor.BlockEdit' )( Edit );

今までregisterBlockType()で渡す設定データのeditsaveなどのコンポーネントが使用される決定的瞬間!
editor.BlockEditもここで使用される。

さてここで出てきたwithFilters()ですが、
withFilters()addFilter()/removeFilter()によってeditor.BlockEditフックに変更があった場合に更新出来るように工夫されたものです。

withFilters()

withFilters()のソースコード

https://github.dev/WordPress/gutenberg/blob/trunk/packages/components/src/higher-order/with-filters/index.tsx

withFilters()のHOCがどういうものかというと、

OriginalComponentを受け取ってFilteredComponentRendererを返す高階関数です。
間にapplyFilter()でフィルタリングしたFilteredComponentの三つが存在します。

それぞれの役割は

コンポーネント名 役割
OriginalComponent 引数で受け取った元のコンポーネント
FilteredComponent applyFilter()でフィルタリングした後のコンポーネント
FilteredComponentRenderer FilteredComponentをレンダリングする用のコンポーネント

です。
このうち引数で受け取るOriginalComponentと、戻り値のFilteredComponentRendererは不変であり、FilteredComponentは可変です。

まずフックは追加したり削除したりすることが出来ることを考えないといけません。
フックの変更があるとそのフィルタリングが返す結果は異なる可能性があります。
そのためフックの変更毎にフィルタリング結果(FilteredComponent)を更新する必要があります。

そこで元のコンポーネントフィルタリングされた後のコンポーネントフィルタリングされたコンポーネントをレンダリングするコンポーネントの三段構えになってます。

ではフックの変更をどう管理しているのでしょう?

FilteredComponentRendererはマウント/アンマウントを監視してます。
そしてインスタンスのコレクションを持っています。
マウントされたらそのインスタンスを追加し、アンマウントされたら削除します。

const FilteredComponentRenderer = withFilters(...)(OriginalComponent);

// 一つ目
<FilteredComponentRenderer />

// 二つ目
<FilteredComponentRenderer />

// 三つ目
<FilteredComponentRenderer />

もし一つ目のコンポーネントがマウントされた時、addAction('hookRemove')及びaddAction('hookAdded')のアクションを登録します。
これらはフックの削除時、追加時に呼ばれるアクションです。
もしwithFilters(フック名)で指定したフック名を受け取った時、

  1. 再フィルタリングしてFilteredComponentを更新する
  2. 監視しているインスタンスをまとめて更新する(forceUpdate())

をします。

つまり、

  1. フィルタの更新を察知
  2. 再フィルタリングでFilteredComponentを更新
  3. コレクションされているFilteredComponentRenderer一覧を再レンダリング

みたいな感じになってます。

アクションとフィルタの一覧を取得

以下のコードはshow()関数を実行するとその時点での登録されたフック名(そのフックを登録した名前空間のリスト)を表示するものです。
特に理由はないですがそれぞれ3つのタイミングで表示してます。

import { actions, addAction, addFilter, filters } from '@wordpress/hooks';

// filters, actions をまとめて表示
function show(nbr: number)
{
    // @ts-ignore
    const actionKeys = Object.entries(actions).map(([k, v]) => `${k} (${v?.handlers?.map(_=>_.namespace).join(', ')})`);

    // @ts-ignore
    const filterKeys = Object.entries(filters).map(([k, v]) => `${k} (${v?.handlers?.map(_=>_.namespace).join(', ')})`);

    console.log(`-------------- Show(${nbr}) ----------------`);
    console.log(`actions\n ${actionKeys.join("\n")}`);
    console.log(`filters\n ${filterKeys.join("\n")}`);
}

show(1);
addAction('hookAdded', 'kurage/hookAdded', fn => {});
show(2);
setTimeout(() => show(3), 2000);

actionsから登録されたアクション一覧を取得します。
filtersから登録されたフィルター一覧を取得します。

show()はそれらの一覧を表示するもので3回呼び出してます。

結果

-------------- Show(1) ----------------
actions
 __current (undefined)
hookAdded (core/i18n)
hookRemoved (core/i18n)
filters
 __current (undefined)
i18n.gettext ()
i18n.gettext_default ()
i18n.gettext_with_context ()
i18n.gettext_with_context_default ()
blocks.registerBlockType (core/compat/migrateLightBlockWrapper, core/align/addAttribute, core/lock/addAttribute, core/anchor/attribute, core/ariaLabel/attribute, core/custom-class-name/attribute, core/border/addAttributes, core/border/addEditProps, core/color/addAttribute, core/color/addEditProps, core/fontFamily/addAttribute, core/fontFamily/addEditProps, core/font/addAttribute, core/font/addEditProps, core/style/addAttribute, core/style/addEditProps, core/settings/addAttribute, core/editor/duotone/add-attributes, core/layout/addAttribute, core/metadata/addMetaAttribute, core/metadata/addLabelCallback, core/font-size/addEditPropsForFluidCustomFontSizes)
editor.BlockListBlock (core/editor/align/with-data-align, core/border/with-border-color-palette-styles, core/color/with-color-palette-styles, core/font-size/with-font-size-inline-styles, core/editor/with-elements-styles, core/editor/duotone/with-styles, core/editor/layout/with-layout-styles)
editor.BlockEdit (core/editor/align/with-toolbar-controls, core/editor/anchor/with-inspector-control, core/editor/custom-class-name/with-inspector-control, core/style/with-block-controls, core/editor/duotone/with-editor-controls, core/editor/layout/with-inspector-controls, core/style/with-block-controls)
blocks.getSaveContent.extraProps (core/align/addAssignedAlign, core/anchor/save-props, core/ariaLabel/save-props, core/custom-class-name/save-props, core/generated-class-name/save-props, core/border/addSaveProps, core/color/addSaveProps, core/fontFamily/addSaveProps, core/font/addSaveProps, core/style/addSaveProps, core/metadata/save-props)
blocks.switchToBlockType.transformedBlock (core/color/addTransforms, core/color/addTransforms, core/font-size/addTransforms)
-------------- Show(2) ----------------
actions
 __current (undefined)
hookAdded (core/i18n, kurage/hookAdded)
hookRemoved (core/i18n)
filters
 __current (undefined)
i18n.gettext ()
i18n.gettext_default ()
i18n.gettext_with_context ()
i18n.gettext_with_context_default ()
blocks.registerBlockType (core/compat/migrateLightBlockWrapper, core/align/addAttribute, core/lock/addAttribute, core/anchor/attribute, core/ariaLabel/attribute, core/custom-class-name/attribute, core/border/addAttributes, core/border/addEditProps, core/color/addAttribute, core/color/addEditProps, core/fontFamily/addAttribute, core/fontFamily/addEditProps, core/font/addAttribute, core/font/addEditProps, core/style/addAttribute, core/style/addEditProps, core/settings/addAttribute, core/editor/duotone/add-attributes, core/layout/addAttribute, core/metadata/addMetaAttribute, core/metadata/addLabelCallback, core/font-size/addEditPropsForFluidCustomFontSizes)
editor.BlockListBlock (core/editor/align/with-data-align, core/border/with-border-color-palette-styles, core/color/with-color-palette-styles, core/font-size/with-font-size-inline-styles, core/editor/with-elements-styles, core/editor/duotone/with-styles, core/editor/layout/with-layout-styles)
editor.BlockEdit (core/editor/align/with-toolbar-controls, core/editor/anchor/with-inspector-control, core/editor/custom-class-name/with-inspector-control, core/style/with-block-controls, core/editor/duotone/with-editor-controls, core/editor/layout/with-inspector-controls, core/style/with-block-controls)
blocks.getSaveContent.extraProps (core/align/addAssignedAlign, core/anchor/save-props, core/ariaLabel/save-props, core/custom-class-name/save-props, core/generated-class-name/save-props, core/border/addSaveProps, core/color/addSaveProps, core/fontFamily/addSaveProps, core/font/addSaveProps, core/style/addSaveProps, core/metadata/save-props)
blocks.switchToBlockType.transformedBlock (core/color/addTransforms, core/color/addTransforms, core/font-size/addTransforms)
react.min.js?ver=17.0.1:9 [Violation] 'message' handler took 186ms
-------------- Show(3) ----------------
actions
 __current (undefined)
hookAdded (core/i18n, kurage/hookAdded, core/with-filters/editor.BlockEdit, core/with-filters/editor.BlockListBlock)
hookRemoved (core/i18n, core/with-filters/editor.BlockEdit, core/with-filters/editor.BlockListBlock)
plugins.pluginRegistered (core/plugins/plugin-area/plugins-registered)
plugins.pluginUnregistered (core/plugins/plugin-area/plugins-unregistered)
heartbeat.send (core/editor/post-locked-modal-0)
heartbeat.tick (core/editor/post-locked-modal-0)
filters
 __current (undefined)
i18n.gettext ()
i18n.gettext_default ()
i18n.gettext_with_context ()
i18n.gettext_with_context_default ()
blocks.registerBlockType (core/compat/migrateLightBlockWrapper, core/align/addAttribute, core/lock/addAttribute, core/anchor/attribute, core/ariaLabel/attribute, core/custom-class-name/attribute, core/border/addAttributes, core/border/addEditProps, core/color/addAttribute, core/color/addEditProps, core/fontFamily/addAttribute, core/fontFamily/addEditProps, core/font/addAttribute, core/font/addEditProps, core/style/addAttribute, core/style/addEditProps, core/settings/addAttribute, core/editor/duotone/add-attributes, core/layout/addAttribute, core/metadata/addMetaAttribute, core/metadata/addLabelCallback, core/editor/custom-sources-backwards-compatibility/shim-attribute-source, block-directory/fallback, core/navigation-link, core/template-part, core/template-part, core/font-size/addEditPropsForFluidCustomFontSizes)
editor.BlockListBlock (core/editor/align/with-data-align, core/border/with-border-color-palette-styles, core/color/with-color-palette-styles, core/font-size/with-font-size-inline-styles, core/editor/with-elements-styles, core/editor/duotone/with-styles, core/editor/layout/with-layout-styles, create-block/kurage-blb)
editor.BlockEdit (core/editor/align/with-toolbar-controls, core/editor/anchor/with-inspector-control, core/editor/custom-class-name/with-inspector-control, core/style/with-block-controls, core/editor/duotone/with-editor-controls, core/editor/layout/with-inspector-controls, core/style/with-block-controls, create-block/kurage-be, core/edit-post/validate-multiple-use/with-multiple-validation, core/query)
blocks.getSaveContent.extraProps (core/align/addAssignedAlign, core/anchor/save-props, core/ariaLabel/save-props, core/custom-class-name/save-props, core/generated-class-name/save-props, core/border/addSaveProps, core/color/addSaveProps, core/fontFamily/addSaveProps, core/font/addSaveProps, core/style/addSaveProps, core/metadata/save-props)
blocks.switchToBlockType.transformedBlock (core/color/addTransforms, core/color/addTransforms, core/font-size/addTransforms, core/gallery/update-third-party-transform-to, core/gallery/update-third-party-transform-from)
editor.Autocomplete.completers (editor/autocompleters/set-default-completers)
editor.MediaUpload (core/edit-post/replace-media-upload)
plugins.registerPlugin ()
blockEditor.__unstableCanInsertBlockType (removeTemplatePartsFromPostTemplates, removeTemplatePartsFromInserter)
blocks.getBlockAttributes ()
blocks.getBlockDefaultClassName ()
blocks.getSaveElement ()

この結果の中でhookAddedのみに注目します。

最初のshow(1)

hookAdded (core/i18n)

2回目はaddAction('hookAdded', 'kurage/hookAdded', fn => {});を呼び出した直後

hookAdded (core/i18n, kurage/hookAdded)

3回目はsetTimeout(() => show(3), 2000);を呼び出した後

hookAdded (core/i18n, kurage/hookAdded, core/with-filters/editor.BlockEdit, core/with-filters/editor.BlockListBlock)

withFilters()hookAddedフックを登録した時の名前がcore/with-filters/editor.BlockEditcore/with-filters/editor.BlockListBlockだと分かります。

BlockListBlockとuseBlockProps()の関係

ドキュメントにwrapperPropsの詳しい解説が無かったので追って見ました。

BlockListBlockwrapperPropsは他のいくつかのプロパティと共にmemoizedValueにメモ化され、
レンダリングでBlockListBlockContextに渡されます。

<BlockListBlockContext.Provider value={ memoizedValue }>
    <BlockCrashBoundary
        fallback={
            <Block className="has-warning">
                <BlockCrashWarning />
            </Block>
        }
    >
        { block }
    </BlockCrashBoundary>
</BlockListBlockContext.Provider>

上記の block の部分が最終的に編集コンポーネントをレンダリングします。

useBlockContext()の定義を見てみます。
ここではBlockListBlockContextのコンテクストが取得されます。
取得されたwrapperProps等は展開され戻り値として返されます。

export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
    const {
        clientId,
        className,
        wrapperProps = {},
        isAligned,
    } = useContext( BlockListBlockContext );

    // 省略

    return ({

        ...wrapperProps,

        // 省略(他にもセレクタ等からいっぱい展開される)

    })

その値は編集コンポーネントで使用できます。

export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
    const cc = useBlockProps();

    return (
        <div {...cc}>

            <div style={{whiteSpace: 'pre'}}>
                { JSON.stringify(cc, null, "\t") }
            </div>

            Hello Kurage

        </div>
    );
}

BlockEditor certificate css DataGrid Docker Gutenberg Hyper-V iframe MUI openssl PHP React ReduxToolkit REST ubuntu WordPress オレオレ認証局 フレームワーク