先に作ったサンプルのブロック―フレームワークに関する説明リストのブロックを元にインナーブロックのTips的なカスタマイズ、活用を紹介します。
参照: Gutenbergのブロックを作る – Autumnsky
ブロック追加時にインナーブロックを予め入れておく
InnerBlocks の template プロパティを設定する。普通の説明リストなら、常に dt ブロックと dd ブロックを組で挿入する等もできる。
src/index.js
edit: ( { className } ) => {
return (
<dl className={ className }>
<InnerBlocks
allowedBlocks={ [ 'framework-dl/description-block' ] }
template={ [ [ 'framework-dl/description-block', {} ] ] }
/>
</dl>
);
},
template プロパティには配列を渡す。配列は[ ブロック名, 引数オブジェクト ]
の配列を1つ以上含める。
これで、flamework 説明ブロックを挿入した時に、description ブロックが一つ入ります。
追加ボタンをカスタマイズする

renderAppender
プロパティを設定すると、Gutenberg デフォルトのbutton-block-appender コンポーネントを上書きして、任意のタグと動作を指定できる。
ただ、このプロパティはハンドブックなどに記載がみつかりませんでした。
edit: ( { className } ) => {
return (
<dl className={ className }>
<InnerBlocks
allowedBlocks={ [ 'framework-dl/description-block' ] }
renderAppender={
// JSXを返す関数 //
}
/>
</dl>
);
},
JSX を返す無名関数を実装。
renderAppender={ () => (
<button
type="button"
onClick={
// ブロックを追加する関数 //
}
>
{ __( 'Add', 'framework-dl-block' ) }
</button>
)
}
クリック時の動作も自分で設定する必要があるので、ブロック追加の関数を改めて実装する。ということで、onClick
メソッドは下記の通り。
onClick={ () => {
dispatch('core/block-editor').insertBlocks(
createBlock('framework-dl/description-block'), 9999, clientId
)
} }
dispatch オブジェクトは@wordperss/data
から、createBlock 関数は@wordpress/blocks
からインポートしておく。
import { registerBlockType, createBlock } from '@wordpress/blocks';
import { InnerBlocks } from '@wordpress/block-editor';
import { dispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
それとclientId
という実行時に割り振られるプロパティが要るが、特に宣言なしで呼び出せるので、edit
メソッドの引数として渡しておく。
edit
メソッド全体は下記の通り。
edit: ( { className, clientId } ) => {
return (
<dl className={ className }>
<InnerBlocks
allowedBlocks={ [ 'framework-dl/description-block' ] }
template={ [ [ 'framework-dl/description-block', {} ] ] }
renderAppender={ () => (
<button
type="button"
onClick={ () => {
dispatch('core/block-editor').insertBlocks(
createBlock('framework-dl/description-block'), 9999, clientId)
} }
>
{ __( 'Add', 'framework-dl-block' ) }
</button>
) }
/>
</dl>
);
},
親ブロックでインナーブロックの値を利用する
インナーブロックの値を取得するにはwithSelect を使います。
例えば、インナーブロックの登場年を取得して { minYear }~{ maxYear }年
という表示をすることもできる。
witheSelect( 関数1 )( 関数2 )
のように書く。関数1にはデータを取得する関数、関数2にレンダー関数などを渡す。withSelect
自身は高階関数のように動作するとのことです。withSelect
は@wordperss/data
からインポートします。カリー化されているので、 witheSelect( 関数1 )
で返ってくる関数に、関数2を渡しているのだと思います。
edit
メソッドに実装するとこうなる。
edit: withSelect( ( select, blockData ) => {
return {
innerBlockProps: select( 'core/block-editor' ).getBlocks( blockData.clientId ),
};
} )( ( { className, clientId, innerBlockProps } ) => {
return (
<dl className={ className }>
…
関数1、関数2ともに無名関数として、その場で作って渡す。関数1でのデータ取得結果は、関数2の引数として利用可能。上記のコードでは、innerBlockProps
プロパティとした。
このinnerBlockProps
の中身は配列になっており、インナーブロックの数だけオブジェクトが格納されている。オブジェクトの中身はインナーブロックの内容である。
(2) […]
0: {…}
attributes: {…}
description: "Framework1 description."
language: ""
name: "Framework1"
since: "2011"
<prototype>: Object { … }
clientId: "60bcf62c-034f-45dd-82ba-f6b1b98c217c"
innerBlocks: Array []
isValid: true
name: "framework-dl/description-block"
originalContent: "<dt>Framework1</dt><div class=\"flex\"><dd><span class=\"since\">2011</span>年~</dd><dd>言語:<span class=\"language\"></span></dd></div><dd class=\"description\">Framework1 description.</dd>"
validationIssues: Array []
<prototype>: Object { … }
1: {…}
attributes: {…}
clientId: "60bcf62c-034f-45dd-82ba-f6b1b98c217c"
innerBlocks: Array []
isValid: true
…
length: 2
<prototype>: Array []
単純にエディター内で表示するだけなら、これでもいい。
…
} )( ( { className, clientId, innerBlockProps } ) => {
const years = innerBlockProps.map( prop => prop.attributes.since );
return (
<>
<p>{ Math.min( ...years ) }~{ Math.max( ...years ) }年</p>
<dl className={ className }>
…
実用的にはattributes
に格納して、save
メソッドでも使えるようにしておく。
attributes: {
minYear: {
type: 'string',
},
maxYear: {
type: 'string',
},
},
edit: withSelect( ( select, blockData ) => {
return {
innerBlockProps: select( 'core/block-editor' ).getBlocks( blockData.clientId ),
};
} )( ( { className, clientId, innerBlockProps, attributes, setAttributes } ) => {
const years = innerBlockProps.map( prop => prop.attributes.since );
setAttributes( { minYear: Math.min( ...years ) } );
setAttributes( { maxYear: Math.max( ...years ) } );
return (
<>
{ attributes.minYear !== attributes.maxYear &&
<p>{ attributes.minYear }~{ attributes.maxYear }年</p>
}
<dl className={ className }>
<InnerBlocks
... 中略 ...
</dl>
</>
);
} ),
save: ( { attributes } ) => {
return (
<div>
{ attributes.minYear !== attributes.maxYear &&
<p>{ attributes.minYear }~{ attributes.maxYear }年</p>
}
<dl>
<InnerBlocks.Content />
</dl>
</div>
);
},
インナーブロックが一つしかないとか、同じ年ばかりだと、2010~2010年 と表示されて不自然なので、条件判定をいれた。
{ attributes.minYear !== attributes.maxYear &&
<p>{ attributes.minYear }~{ attributes.maxYear }年</p>
}
withSelectに関しては、文法的なところも今ひとつ理解できていないし、ハンドブックにもEditメソッドでの使い方の記載がないんですが、Gutenberg のイッシューで「Edit に渡すなら withSelect を使う」と返答があって、実際これで期待通り動きます。
How to know if an InnerBlock is selected · Issue #22282 · WordPress/gutenberg
withSelect関数自身は、内部的にはReactのフックを呼んでいるらしい。
参考: Introducing useDispatch and useSelect – Make WordPress Core
ブロック内部のデータを取得するだけなら、useSelect関数を使うのがよいそうです。
全部入りコード
ここまでのTipsを全て組み込むと、コードはこの通り。
@wordpress/create-block でインストールしたリンターに基づいて、コーディングスタイルの整形をかけてあります。
import { registerBlockType, createBlock } from '@wordpress/blocks';
import { InnerBlocks } from '@wordpress/block-editor';
import { dispatch, withSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import './editor.scss';
import './style.scss';
import './framework-dd-block.js';
registerBlockType( 'framework-dl/framework-dl-block', {
title: __( 'Framework description list', 'framework-dl-block' ),
description: __(
'Example block written with ESNext standard and JSX support – build step required.',
'framework-dl-block'
),
category: 'widgets',
icon: 'feedback',
attributes: {
minYear: {
type: 'string',
},
maxYear: {
type: 'string',
},
},
edit: withSelect( ( select, blockData ) => {
return {
innerBlockProps: select( 'core/block-editor' ).getBlocks(
blockData.clientId
),
};
} )(
( {
className,
clientId,
innerBlockProps,
attributes,
setAttributes,
} ) => {
const years = innerBlockProps.map(
( prop ) => prop.attributes.since
);
setAttributes( { minYear: Math.min( ...years ) } );
setAttributes( { maxYear: Math.max( ...years ) } );
return (
<>
{ attributes.minYear !== attributes.maxYear && (
<p>
{ attributes.minYear }~{ attributes.maxYear }年
</p>
) }
<dl className={ className }>
<InnerBlocks
allowedBlocks={ [
'framework-dl/description-block',
] }
template={ [
[ 'framework-dl/description-block', {} ],
] }
renderAppender={ () => (
<button
type="button"
onClick={ () => {
dispatch(
'core/block-editor'
).insertBlocks(
createBlock(
'framework-dl/description-block'
),
9999,
clientId
);
} }
>
{ __( 'Add', 'framework-dl-block' ) }
</button>
) }
/>
</dl>
</>
);
}
),
save: ( { attributes } ) => {
return (
<div>
{ attributes.minYear !== attributes.maxYear && (
<p>
{ attributes.minYear }~{ attributes.maxYear }年
</p>
) }
<dl>
<InnerBlocks.Content />
</dl>
</div>
);
},
} );