gutenbergdocs/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md
2025-10-22 01:40:18 +08:00

10 KiB
Raw Permalink Blame History

使用 React 钩子

你可以使用名为 useInnerBlocksProps 的 React 钩子来替代 InnerBlocks 组件。该钩子能让你更灵活地控制内部区块区域的标记结构。

useInnerBlocksPropsInnerBlocks 组件同样从 @wordpress/block-editor 包中导出,并支持该组件的所有功能。其工作方式类似于 useBlockProps 钩子。

需特别注意:必须在调用 useInnerBlocksProps 之前 调用 useBlockProps 钩子,否则 useBlockProps 将返回空对象。

以下是基础的 useInnerBlocksProps 钩子用法:

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

registerBlockType( 'gutenberg-examples/example-06', {
	// ...

	edit: () => {
		const blockProps = useBlockProps();
		const innerBlocksProps = useInnerBlocksProps();

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

	save: () => {
		const blockProps = useBlockProps.save();
		const innerBlocksProps = useInnerBlocksProps.save();

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

该钩子还可将 useBlockProps 钩子返回的对象传递给 useInnerBlocksProps 钩子,从而减少需要创建的元素数量:

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

registerBlockType( 'gutenberg-examples/example-06', {
	// ...

	edit: () => {
		const blockProps = useBlockProps();
		const innerBlocksProps = useInnerBlocksProps( blockProps );

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

	save: () => {
		const blockProps = useBlockProps.save();
		const innerBlocksProps = useInnerBlocksProps.save( blockProps );

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

上述代码将在编辑器中渲染为以下标记结构:

<div>
	<!-- 内部区块将插入在此处 -->
</div>

使用钩子方案的另一个优势是:可以利用返回值(仅为一个对象)并通过解构获取其中的 React 子元素。该属性包含实际的子内部区块,因此我们可以将元素放置在与内部区块同一层级:

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

registerBlockType( 'gutenberg-examples/example-06', {
	// ...

	edit: () => {
		const blockProps = useBlockProps();
		const { children, ...innerBlocksProps } = useInnerBlocksProps( blockProps );

		return (
			<div {...innerBlocksProps}>
    			{ children }
				<!-- 可在此处插入与子元素同层级的任意HTML -->
			</div>
		);
	},

	// ...
} );
<div>
	<!-- 内部区块将插入在此处 -->
	<!-- 自定义HTML将渲染于同一层级 -->
</div>

在区块中使用父级、祖先与子级关系

使用 InnerBlocks 的常见模式是创建一个仅在其父级区块插入时才可用的自定义区块。这使构建者能够在建立区块间关系的同时,限制嵌套区块的可发现性。构建者可使用三种关系类型:parent(父级)、ancestor(祖先)和 allowedBlocks(允许的区块)。它们的区别在于:

  • 若指定 parent则表示嵌套区块仅能作为__父级的直接后代__被使用和插入
  • 若指定 ancestor则表示嵌套区块仅能作为__父级的后代__被使用和插入
  • 若指定 allowedBlocks则表示反向关系——即哪些区块可以作为__此区块的直接后代__被使用和插入

parentancestor 的核心区别在于:parent 具有更精确的特定性,而 ancestor 在嵌套层级中具有更高灵活性。

定义父级区块关系

典型案例是 Column区块它被设置了 parent 区块配置。这使得 Column 区块仅能作为其父级 Columns列组区块的嵌套直接后代使用。否则该区块将不会在区块插入器中显示。参阅 Column 代码实现

定义直接后代区块时,需使用 parent 区块配置来声明父级区块。这能防止嵌套区块在其定义的 InnerBlock 之外显示于插入器中。

{
	"title": "栏目",
	"name": "core/column",
	"parent": [ "core/columns" ],
	// ...
}

定义祖先区块关系

典型案例是 Comment Author Name评论作者名称区块它被设置了 ancestor 区块配置。这使得该区块仅能作为其祖先 Comment Template评论模板区块的嵌套后代使用。否则该区块将不会在区块插入器中显示。参阅 Comment Author Name 代码实现

通过 ancestor 关系Comment Author Name 区块可以存在于层级树中的任意位置而__不仅限于__父级 Comment Template 区块的直接子级,同时仍能限制其在区块插入器中的可见性——仅当 Comment Template 区块存在时才显示为可插入选项。

定义后代区块时,需使用 ancestor 区块配置。这能防止嵌套区块在其定义的 InnerBlock 之外显示于插入器中。

{
	"title": "评论作者名称",
	"name": "core/comment-author-name",
	"ancestor": [ "core/comment-template" ],
	// ...
}

定义子级区块关系

典型案例是 Navigation导航区块它被设置了 allowedBlocks 区块配置。这使得仅特定类型的区块能作为 Navigation 区块的直接后代使用。参阅 Navigation 代码实现

自定义区块构建者可扩展 allowedBlocks 设置。通过挂载 blocks.registerBlockType 过滤器,自定义区块可将自身添加到 Navigation 的可用子级列表中。

定义可能的子代区块集合时,需使用 allowedBlocks 区块配置。这会限制插入新子区块时插入器中显示的区块类型。

{
	"title": "导航",
	"name": "core/navigation",
	"allowedBlocks": [ "core/navigation-link", "core/search", "core/social-links", "core/page-list", "core/spacer" ],
	// ...
}

嵌套区块:使用 InnerBlocks

您可以使用 InnerBlocks 组件创建包含其他区块的独立区块。这一技术被广泛应用于分栏区块、社交链接区块等需要容纳其他区块的组件中。

注意:单个区块只能包含一个 InnerBlocks 组件。

以下是 InnerBlocks 的基本用法:

import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';

registerBlockType( 'gutenberg-examples/example-06', {
	// ...

	edit: () => {
		const blockProps = useBlockProps();

		return (
			<div { ...blockProps }>
				<InnerBlocks />
			</div>
		);
	},

	save: () => {
		const blockProps = useBlockProps.save();

		return (
			<div { ...blockProps }>
				<InnerBlocks.Content />
			</div>
		);
	},
} );

允许的区块

通过 allowedBlocks 属性,您可以在 block.jsonallowedBlocks 字段基础上,进一步限制哪些区块可以作为该区块的直接子级插入。这对于针对每个区块动态确定允许的区块列表非常实用,例如根据区块属性来决定:

const { allowedBlocks } = attributes;
//...
<InnerBlocks allowedBlocks={ allowedBlocks } />;

如果允许的区块列表始终保持不变,建议改用 allowedBlocks 区块设置

排列方向

默认情况下,InnerBlocks 假定其包含的区块以垂直列表形式呈现。在某些应用场景中,可能需要通过为内部区块包装器添加 CSS 弹性布局或网格属性,使内部区块水平排列。当区块采用此类样式时,可通过设置 orientation 属性来指示当前正在使用水平布局:

<InnerBlocks orientation="horizontal" />

设置此属性不会影响内部区块的布局,但会使子区块中的区块移动器图标水平显示,同时确保拖放功能正常工作。

默认区块

默认情况下,当点击区块添加器时,InnerBlocks 会通过 allowedBlocks 显示允许的区块列表。您可以使用 defaultBlock 属性来修改点击初始区块添加器时插入的默认区块及其属性。例如:

<InnerBlocks defaultBlock={['core/paragraph', {placeholder: "Lorem ipsum..."}]} directInsert />

默认情况下,此功能处于禁用状态,除非将 directInsert 属性设置为 true。这使您能够指定默认区块是否应该插入的条件。

模板

使用 template 属性可以定义一组区块,在 InnerBlocks 组件没有现有内容时预填充内容。您可以为这些区块设置属性来定义其用途。以下示例展示了使用 InnerBlocks 组件设置书评模板,并通过占位符值展示区块用途:

const MY_TEMPLATE = [
	[ 'core/image', {} ],
	[ 'core/heading', { placeholder: '书名' } ],
	[ 'core/paragraph', { placeholder: '内容摘要' } ],
];

//...

	edit: () => {
		return (
			<InnerBlocks
				template={ MY_TEMPLATE }
				templateLock="all"
			/>
		);
	},

使用 templateLock 属性可以锁定模板。设置为 all 时将完全锁定模板,禁止任何修改;设置为 insert 时则仅允许重新排序现有区块,禁止插入新区块。更多信息请参阅 templateLock 文档

文章模板

虽然与 InnerBlocks 无直接关联,但值得在此说明:您可以按文章类型创建文章模板,使区块编辑器预加载一组特定区块。

InnerBlocks 模板仅适用于您创建的单个区块组件,而文章的其余部分可以包含用户喜欢的任何区块。使用文章模板则可以将整篇文章锁定为您定义的模板结构。

add_action( 'init', function() {
	$post_type_object = get_post_type_object( 'post' );
	$post_type_object->template = array(
		array( 'core/image' ),
		array( 'core/heading' )
	);
} );