### 数据库中动态区块的HTML呈现方式（`save`方法）

对于动态区块，`save`回调函数可以直接返回`null`，这将告知编辑器仅将区块分隔注释（以及现有的[区块属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/)）保存至数据库。这些属性随后会被传递到服务端渲染回调函数，由该函数决定如何在前端站点上显示区块。

当`save`返回`null`时，区块编辑器将跳过[区块标记验证流程](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#validation)，避免因频繁变化的标记引发问题。

具有动态渲染功能的区块也可以保存区块的HTML表示作为备用方案。如果您提供了服务端渲染回调，数据库中代表区块的HTML将被回调输出所替代，但若区块被停用（注册区块的插件被卸载）或渲染回调被移除，则会呈现原保存的HTML。

在某些情况下，区块会保存其HTML表示，并在满足特定条件时使用动态渲染来优化标记。核心区块中使用此方法的示例包括：

- [封面区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/cover)通过[save方法](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/cover/save.js)在数据库中保存完整的HTML表示。该标记通过[`render_callback`](https://github.com/WordPress/gutenberg/blob/22741661998834e69db74ad863705ee2ce97b446/packages/block-library/src/cover/index.php#L74)处理，当启用“使用特色图像”设置时，会[动态注入](https://github.com/WordPress/gutenberg/blob/22741661998834e69db74ad863705ee2ce97b446/packages/block-library/src/cover/index.php#L16)特色图像。
- [图像区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/image)同样通过[save方法](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/image/save.js)将其HTML表示保存至数据库。该标记通过[`render_callback`](https://github.com/WordPress/gutenberg/blob/22741661998834e69db74ad863705ee2ce97b446/packages/block-library/src/image/index.php#L363)处理，当满足特定条件时，会[为标记添加额外属性](https://github.com/WordPress/gutenberg/blob/22741661998834e69db74ad863705ee2ce97b446/packages/block-library/src/image/index.php#L18)。

如果在动态区块中使用[内部区块](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/)，您需要通过`<InnerBlocks.Content/>`在`save`回调函数中保存`InnerBlocks`。

## 扩展资源

- [静态区块 vs 动态区块：差异解析](https://developer.wordpress.org/news/2023/02/27/static-vs-dynamic-blocks-whats-the-difference/) | 开发者博客
- [区块弃用机制详解](https://developer.wordpress.org/news/2023/03/10/block-deprecation-a-tutorial/) | 开发者博客

# 区块的静态与动态渲染

区块的前端标记既可以在请求时由服务器端动态生成（动态区块），也可以在区块编辑器的保存过程中静态生成（静态区块）。本文将深入探讨这两种实现方式。

<div class="callout callout-tip">
<a href="https://developer.wordpress.org/news/2023/02/27/static-vs-dynamic-blocks-whats-the-difference/">《静态区块与动态区块：差异解析》</a>这篇技术文章为本主题提供了绝佳的入门指引。
</div>

## 静态渲染

采用"静态渲染"的区块在保存时就会生成固定不变的前端输出并存储至数据库。这类区块完全依赖其 `save` 函数来定义其 [HTML 标记结构](https://developer.wordpress.org/block-editor/getting-started/fundamentals/markup-representation-block/)，除非在区块编辑器中进行手动修改，否则这些标记将始终保持不变。

若某个区块未采用动态渲染方式——即不通过 PHP 在页面加载时实时生成内容——则会被归类为"静态区块"。

下图直观展示了静态区块内容如何保存至数据库，并在前端被检索并渲染为 HTML 的过程。

![静态渲染区块示意图](https://developer.wordpress.org/files/2024/01/static-rendering.png)

### 如何为区块定义静态渲染

通过[在客户端注册区块](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registration-of-the-block-with-javascript-client-side)时可定义的 `save` 函数，可以指定区块的 HTML 结构。每当在编辑器中保存区块时，该 HTML 结构就会被存入数据库，并最终用于前端展示。

WordPress 中的区块会被封装在特殊的注释标签内，这些标签作为唯一的[区块定界符](https://developer.wordpress.org/block-editor/getting-started/fundamentals/markup-representation-block/)存在。但最终渲染时，只会呈现静态区块 `save` 函数中定义的 HTML 内容（不包含这些定界符）。

<details><summary><strong>查看预格式化区块中的静态渲染示例</strong></summary>
<br/>
以下是 <a href="https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/preformatted">预格式化</a> 核心区块的 <a href="https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/preformatted/save.js"><code>save</code> 函数</a>示例：

```js
import { RichText, useBlockProps } from '@wordpress/block-editor';

export default function save( { attributes } ) {
	const { content } = attributes;

	return (
		<pre { ...useBlockProps.save() }>
			<RichText.Content value={ content } />
		</pre>
	);
}
```

当 `attributes.content` 的值为 `"这是一段预格式化文本"` 时，该函数会生成如下区块标记表示：

```html
<!-- wp:preformatted -->
<pre class="wp-block-preformatted">这是一段预格式化文本</pre>
<!-- /wp:preformatted -->
```

在前端，该区块将返回以下标记。请注意定界符已不复存在。

```html
<pre class="wp-block-preformatted">这是一段预格式化文本</pre>
```
</details>
<br/>

我们将在下一节探讨的动态区块，虽然也能通过 `save` 函数指定初始 HTML 结构（类似于静态区块），但它们主要依赖服务器端渲染来生成内容。如果因故无法进行动态渲染（比如区块插件被停用），系统将转而使用数据库中保存的 HTML 结构来在前端显示该区块。

要了解具体运作机制，请参阅[构建你的第一个区块](/docs/getting-started/tutorial.md)教程。特别是[添加静态渲染](/docs/getting-started/tutorial.md#adding-static-rendering)章节，生动演示了区块如何同时具备保存 HTML 结构和动态渲染能力。

<div class="callout callout-info">
WordPress 提供了诸如 <a href="https://developer.wordpress.org/reference/functions/render_block/"><code>render_block</code></a> 和 <a href="https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/creating-dynamic-blocks/"><code>render_callback</code></a> 等机制，用于在区块最终呈现到前端之前修改其保存的 HTML。这些工具赋予开发者动态定制区块输出的能力，以满足复杂交互场景的需求。
</div>

更多采用静态渲染的 WordPress 区块示例（即其输出在保存时即固定，不依赖服务器端处理）包括：

- [分隔符区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/separator/save.js)
- [间距区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/spacer/save.js)
- [按钮区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/button/save.js)

## 动态渲染

具备“动态渲染”功能的区块旨在前端请求时实时生成其内容和结构。与在数据库中保存固定HTML结构的静态区块不同，“动态区块”依赖服务器端处理来动态构建输出，这使其具有高度灵活性，非常适合需要频繁更新或依赖外部数据的内容。

下图展示了动态区块的表示形式如何在数据库中保存，然后在前端检索并动态渲染为HTML。

![动态渲染区块示意图](https://developer.wordpress.org/files/2024/01/dynamic-rendering.png)

动态区块的常见应用场景包括：

1. **内容需在文章未更新时自动变化的区块**：例如[最新文章](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/latest-posts)区块，每当有新文章发布时会自动更新。
2. **标记更新需立即在前端显示的区块**：若通过添加新类、HTML元素或任何方式更新区块结构，使用动态区块可确保这些更改立即应用到全站所有该区块实例。若无动态区块，类似更新可能触发区块编辑器中的[验证错误](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#validation)。

### 如何为区块定义动态渲染

区块可通过两种主要方式定义动态渲染：

1. 使用可传递给[`register_block_type()`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registration-of-the-block-with-php-server-side)函数的`render_callback`参数。这对于[纯PHP区块](/docs/getting-started/fundamentals/registration-of-a-block.md#php-only-blocks-with-auto-registration)是必需的。
2. 使用通常命名为`render.php`的独立PHP文件。该文件路径应在`block.json`文件中通过[`render`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#files-for-the-blocks-behavior-output-or-style)属性定义。

这两种方法都会接收以下数据：

 - `$attributes`：区块的属性数组。
 - `$content`：数据库中存储的区块标记（如有）。
 - `$block`：表示已渲染区块的[WP_Block](https://developer.wordpress.org/reference/classes/wp_block/)类实例（[区块元数据](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/)）。

<details><summary><strong>查看站点标题区块中的动态渲染示例</strong></summary>
<br/>

[站点标题](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/site-title)区块使用以下[`render_callback`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/site-title/index.php)：

```php
function render_block_core_site_title( $attributes ) {
	$site_title = get_bloginfo( 'name' );
	if ( ! $site_title ) {
		return;
	}

	$tag_name = 'h1';
	$classes  = empty( $attributes['textAlign'] ) ? '' : "has-text-align-{$attributes['textAlign']}";
	if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
		$classes .= ' has-link-color';
	}

	if ( isset( $attributes['level'] ) ) {
		$tag_name = 0 === $attributes['level'] ? 'p' : 'h' . (int) $attributes['level'];
	}

	if ( $attributes['isLink'] ) {
		$aria_current = is_home() || ( is_front_page() && 'page' === get_option( 'show_on_front' ) ) ? ' aria-current="page"' : '';
		$link_target  = ! empty( $attributes['linkTarget'] ) ? $attributes['linkTarget'] : '_self';

		$site_title = sprintf(
			'<a href="%1$s" target="%2$s" rel="home"%3$s>%4$s</a>',
			esc_url( home_url() ),
			esc_attr( $link_target ),
			$aria_current,
			esc_html( $site_title )
		);
	}
	$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classes ) ) );

	return sprintf(
		'<%1$s %2$s>%3$s</%1$s>',
		$tag_name,
		$wrapper_attributes,
		// 如果是链接则已预转义
		$attributes['isLink'] ? $site_title : esc_html( $site_title )
	);
}
```

但该区块未定义`save`函数，从其[`index.js`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/site-title/index.js)文件可见，这意味着区块在数据库中的标记表示如下：

```html
<!-- wp:site-title /-->
```

在前端，`render_callback`会根据请求区块时服务器的具体值动态渲染区块标记。这些值包括当前站点标题、URL、链接目标等。

```
<h1 class="wp-block-site-title"><a href="https://www.wp.org" target="_self" rel="home">我的WordPress网站</a></h1>
```

</details>
<br/>