## 使用 React 钩子

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

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

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

以下是基础的 `useInnerBlocksProps` 钩子用法：

```js
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` 钩子，从而减少需要创建的元素数量：

```js
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} />
		);
	},
} );
```

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

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

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

```js
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>
		);
	},

	// ...
} );
```

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

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

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

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

`parent` 与 `ancestor` 的核心区别在于：`parent` 具有更精确的特定性，而 `ancestor` 在嵌套层级中具有更高灵活性。

### 定义父级区块关系

典型案例是 Column（列）区块，它被设置了 `parent` 区块配置。这使得 Column 区块仅能作为其父级 Columns（列组）区块的嵌套直接后代使用。否则该区块将不会在区块插入器中显示。参阅 [Column 代码实现](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src/column)。

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

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

### 定义祖先区块关系

典型案例是 Comment Author Name（评论作者名称）区块，它被设置了 `ancestor` 区块配置。这使得该区块仅能作为其祖先 Comment Template（评论模板）区块的嵌套后代使用。否则该区块将不会在区块插入器中显示。参阅 [Comment Author Name 代码实现](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src/comment-author-name)。

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

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

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

### 定义子级区块关系

典型案例是 Navigation（导航）区块，它被设置了 `allowedBlocks` 区块配置。这使得仅特定类型的区块能作为 Navigation 区块的直接后代使用。参阅 [Navigation 代码实现](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src/navigation)。

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

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

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

# 嵌套区块：使用 InnerBlocks

您可以使用 [InnerBlocks](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/inner-blocks/README.md) 组件创建包含其他区块的独立区块。这一技术被广泛应用于分栏区块、社交链接区块等需要容纳其他区块的组件中。

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

以下是 InnerBlocks 的基本用法：

```js
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.json` 的 `allowedBlocks` 字段基础上，进一步限制哪些区块可以作为该区块的直接子级插入。这对于针对每个区块动态确定允许的区块列表非常实用，例如根据区块属性来决定：

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

如果允许的区块列表始终保持不变，建议改用 [`allowedBlocks` 区块设置](#定义子级区块关系)。

## 排列方向

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

```js
<InnerBlocks orientation="horizontal" />
```

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

## 默认区块

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

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

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

## 模板

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

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

//...

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

使用 `templateLock` 属性可以锁定模板。设置为 `all` 时将完全锁定模板，禁止任何修改；设置为 `insert` 时则仅允许重新排序现有区块，禁止插入新区块。更多信息请参阅 [templateLock 文档](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/inner-blocks/README.md#templatelock)。

### 文章模板

虽然与 `InnerBlocks` 无直接关联，但值得在此说明：您可以按文章类型创建[文章模板](https://developer.wordpress.org/block-editor/developers/block-api/block-templates/)，使区块编辑器预加载一组特定区块。

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

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