# 创建动态区块

动态区块是在前端渲染时实时构建结构和内容的区块。

动态区块主要有两个用途：

1. **内容需要实时更新的区块**：即使文章未更新，区块内容也会自动变化。WordPress 自带的「最新文章」区块就是典型示例——每当发布新文章时，所有使用该区块的位置都会同步更新。
2. **需要即时呈现代码更新的区块**：当修改区块代码（HTML/CSS/JS）时，使用动态区块可确保修改立即在全站所有该区块实例上生效。（若不使用动态区块，Gutenberg 的[验证流程](/docs/reference-guides/block-api/block-edit-save.md#validation)会触发，导致用户看到“此区块似乎已被外部修改”的提示）

对于多数动态区块，`save` 回调函数应返回 `null`，这会让编辑器仅将[区块属性](/docs/reference-guides/block-api/block-attributes.md)保存至数据库。这些属性随后会传入服务端渲染回调，从而由开发者决定如何在前端展示区块。返回 `null` 时，编辑器会跳过区块标记验证流程，避免因频繁变化的标记引发问题。

若在动态区块中使用 [InnerBlocks](/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md)，需通过 `<InnerBlocks.Content/>` 在 `save` 回调中保存内部区块内容。

也可保存区块的 HTML 表示形式。若设置了服务端渲染回调，该 HTML 会被回调输出替换；但当区块被停用或渲染回调被移除时，此 HTML 仍会正常渲染。

区块属性可用于保存任何需要存储的内容或设置。例如最新文章区块中，可将要显示的文章数量保存为属性；又或者针对需要在前端展示的每项内容（如标题文本、段落文字、图片、链接等），都可通过属性进行配置。

以下代码示例展示了如何创建仅显示最新文章链接的动态区块：

```jsx
import { registerBlockType } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { useBlockProps } from '@wordpress/block-editor';

registerBlockType( 'gutenberg-examples/example-dynamic', {
	apiVersion: 3,
	title: '示例：最新文章',
	icon: 'megaphone',
	category: 'widgets',

	edit: () => {
		const blockProps = useBlockProps();
		const posts = useSelect( ( select ) => {
			return select( 'core' ).getEntityRecords( 'postType', 'post' );
		}, [] );

		return (
			<div { ...blockProps }>
				{ ! posts && '加载中' }
				{ posts && posts.length === 0 && '暂无文章' }
				{ posts && posts.length > 0 && (
					<a href={ posts[ 0 ].link }>
						{ posts[ 0 ].title.rendered }
					</a>
				) }
			</div>
		);
	},
} );
```

由于是动态区块，无需在客户端重写默认的 `save` 实现，但需要配置服务端组件。前端显示内容取决于 `register_block_type` 的 `render_callback` 属性所调用的函数。

```php
<?php

/**
 * 插件名称：Gutenberg 动态区块示例
 */

function gutenberg_examples_dynamic_render_callback( $block_attributes, $content ) {
	$recent_posts = wp_get_recent_posts( array(
		'numberposts' => 1,
		'post_status' => 'publish',
	) );
	if ( count( $recent_posts ) === 0 ) {
		return '暂无文章';
	}
	$post = $recent_posts[ 0 ];
	$post_id = $post['ID'];
	return sprintf(
		'<a class="wp-block-my-plugin-latest-post" href="%1$s">%2$s</a>',
		esc_url( get_permalink( $post_id ) ),
		esc_html( get_the_title( $post_id ) )
	);
}

function gutenberg_examples_dynamic() {
	// 自动加载依赖项与版本号
	$asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');

	wp_register_script(
		'gutenberg-examples-dynamic',
		plugins_url( 'build/block.js', __FILE__ ),
		$asset_file['dependencies'],
		$asset_file['version']
	);

	register_block_type( 'gutenberg-examples/example-dynamic', array(
		'api_version' => 3,
		'editor_script' => 'gutenberg-examples-dynamic',
		'render_callback' => 'gutenberg_examples_dynamic_render_callback'
	) );

}
add_action( 'init', 'gutenberg_examples_dynamic' );
```

需要注意以下几点：
- `edit` 函数仍在编辑器中展示区块预览（可与渲染版本完全不同，由区块作者决定）
- 内置 `save` 函数直接返回 `null`，因为渲染在服务端完成
- 服务端渲染函数接收区块属性及内部内容作为参数，返回标记（与短代码机制高度相似）

**注意：** 关于颜色、边框、间距等常见自定义设置，我们将在[下一章](/docs/how-to-guides/block-tutorial/block-supports-in-dynamic-blocks.md)了解如何通过区块支持功能高效实现。

## 在区块编辑器中实时渲染

Gutenberg 2.8 版本引入了 [`<ServerSideRender>`](/packages/server-side-render/README.md) 区块，支持在服务端使用 PHP 进行渲染而非 JavaScript。

*服务端渲染仅作为降级方案，始终推荐使用客户端 JavaScript 渲染（速度更快且支持更灵活的编辑器操作）。*

```jsx
import { registerBlockType } from '@wordpress/blocks';
import ServerSideRender from '@wordpress/server-side-render';
import { useBlockProps } from '@wordpress/block-editor';

registerBlockType( 'gutenberg-examples/example-dynamic', {
	apiVersion: 3,
	title: '示例：最新文章',
	icon: 'megaphone',
	category: 'widgets',

	edit: function ( props ) {
		const blockProps = useBlockProps();
		return (
			<div { ...blockProps }>
				<ServerSideRender
					block="gutenberg-examples/example-dynamic"
					attributes={ props.attributes }
				/>
			</div>
		);
	},
} );
```

注意此代码使用了 `wp-server-side-render` 包但未使用 `wp-data`，请确保在 PHP 代码中更新依赖项。建议使用 wp-scripts 自动构建依赖（具体 PHP 代码配置可参考 [block-development-examples 代码库](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/basic-esnext-a2ab62)）。