無駄と文化

実用的ブログ

インナーブロックの情報を親ブロックから取得する (WordPress, カスタムブロック)

Gutenberg のカスタムブロックでインナーブロックを配置できるようにしたとき、親ブロックからインナーブロックの情報を取得する方法をまとめます。

 

以下、 @wordpress/create-block を使ってひな形を作成したときのファイル構成を前提に解説します。

インナーブロックを配置できるようにしたときの最小構成

まず、普通に最小構成でブロックを構築するとこんな感じになります。

src/edit.js

import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

export function Edit() {
    const blockProps = useBlockProps();
    const innerBlocksProps = useInnerBlocksProps( blockProps, {} );

    return (
        <div { ...innerBlocksProps  } />
    );
}

やりたいこと

useInnerBlocksProps を利用すると、簡単お手軽にインナーブロックを配置できるブロックが作れます。
save() 関数側でも <InnerBlocks.Content /> と書くだけでインナーブロックの管理はお任せできます。

しかし、 "お任せできる" というのは言い換えると "ブラックボックスになっていて外から中の状態を知り得ない" ということです。それでは困る場面もあるでしょう。
というわけで親ブロックからインナーブロックの情報を取得してみましょう。

useSelect を使ってインナーブロックの情報を取得する

インナーブロックの情報を取得するために useSelect を使います。

src/edit.js

import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';

export function Edit({ clientId }) {
    const innerBlocks = useSelect( select => select( 'core/block-editor' ).getBlocks( clientId ) );
    console.log( { innerBlocks  } );

    const blockProps = useBlockProps();
    const innerBlocksProps = useInnerBlocksProps(blockProps, {});

    return (
        <div { ...innerBlocksProps  } />
    );
}

useSelect を使って useSelect( select => select( 'core/block-editor' ).getBlocks( clientId ) ) のように書くとインナーブロックの情報を取得できます。
getBlocks() メソッドには引数として clientId を渡す必要があります。 Edit() のオブジェクト引数から取得できる clientId をそのまま渡せば大丈夫です。

取得した innerBlocks を実際に見てみると、以下のようなデータになっています。

[
    {
        "clientId": "0c8bc6d7-4b66-46e7-a1aa-5f7284498655",
        "name": "core/heading",
        "isValid": true,
        "attributes": {
            "content": "Foo Bar",
            "level": 1
        },
        "innerBlocks": []
    },
    {
        "clientId": "7e1d6dd0-4152-41a6-98ca-205db0b29a9e",
        "name": "core/paragraph",
        "isValid": true,
        "attributes": {
            "content": "Hoge Fuga",
            "dropCap": false
        },
        "innerBlocks": []
    }
]

attributes プロパティを参照することでインナーブロックのコンテンツに設定されているテキストなどが直接手に入ります。
その他 innerBlocks プロパティを参照すると、「インナーブロックの中のインナーブロック」まで辿っていくこともできます。

withSelect を使ってインナーブロックの情報を取得する

useSelect の代わりに withSelect を使ってもインナーブロックの情報を取得できます。
useSelect を使ったときと理屈は同じです。

src/edit.js

import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
import { withSelect } from '@wordpress/data';

export const Edit = withSelect( ( select, { clientId } ) => ({
    innerBlocks: select( 'core/block-editor' ).getBlocks( clientId ),
}))(edit);

function edit({ innerBlocks }) {
  console.log( { innerBlocks  } );

  const blockProps = useBlockProps();
  const innerBlocksProps = useInnerBlocksProps(blockProps, {});

  return (
      <div { ...innerBlocksProps  } />
  );
}

withSelect() の返り値で edit() 関数をデコレートしてあげることで、 edit() 関数のオブジェクト引数に { innerBlocks: [ ... ] } という形でインナーブロックの情報が引き渡されます。

まとめ

@wordpress/data の API 、挙動がマジカルすぎて理解が難しい。

 

私からは以上です。

参考

github.com

history.back() でページを戻ったときの JavaScript の挙動

「ページ A」から「ページ B」へ遷移した後に「ページ B」で hidtory.back() が実行され「ページ A」に戻ってきたとき、「ページ A」で走っていたスクリプトの挙動について考えよう。

f:id:todays_mitsui:20220214191042p:plain

ちなみにブラウザの「戻る」ボタンでページを戻ったときにも全く同じ議論が通じる。

 

TL;DR

  • ページ A で window.addEventListener('unload', ...) されているときは、JavaScript の状態はリセットされ再実行される
  • ページ A で window.addEventListener('unload', ...) されていないときは、JavaScript の状態はリセットされずに再開される
  • 下記の参考ページを読んで

nmi.jp

 

hidtory.back() 後に JavaScript の挙動おかしくなりがち

長年 hidtory.back() 後の JavaScript の挙動に悩まされてきた。

例えばあるとき、 Vue.js 製のフォームで hidtory.back() 後にフォームの入力がリセットされていることが問題になった。
しかも厄介なのは、この問題が再現する場合としない場合があった。

というわけで本腰入れて調べた結果、上記に挙げた参考サイトにたどり着いた。
ページ A に window.addEventListener('unload', ...) があるか否かによって hidtory.back() 後のスクリプトの挙動が変わる。

 

window.addEventListener('unload', ...) があるとき

hidtory.back() 後には JavaScript の状態がリセットされて、最初からスクリプトが再実行される。
Vue.js や React のような 要素を動的に作るライブラリ を使用している場合、状態をリセットして <input> タグを生成しなおすので入力内容は消えてしまう。

 

window.addEventListener('unload', ...) が無いとき

hidtory.back() 後に JavaScript の状態リセットはされず、ページ遷移した直後からスクリプトが再開するように見える。
<input> タグが生成された後の状態から再開するので入力内容は生きる。

 

ちなみに

window.addEventListener('unload', ...) ではなく、古き良き window.onunload = function() { ... } の形でリスナーを設定していても、同じ挙動になる。

 

誰が window.addEventListener('unload', ...) していたのか

個人の観測範囲では、 unload イベントにリスナーを設定することは滅多にしないことだ。
私の手元の環境では Facebook ピクセルwindow.addEventListener('unload', ...) していた。そのため Facebook 広告を配信しているか否かによって hidtory.back() 後の挙動が違って見えていた。

 

まとめ

挙動がまちまちだと問題になるが、挙動を均一化してしまえば何らかの対処ができると思う。
私の場合は問題がおこるページに window.addEventListener('unload', () => {}) を設定し、 window.addEventListener('unload', ...) があるときの挙動に寄せて対処した。

ほんと勘弁して。

 

私からは以上です。