# 区块的静态或动态渲染

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

<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)教程。特别是[添加静态渲染](/docs/getting-started/tutorial#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 区块示例（即其输出在保存时即已固定，不依赖于服务器端处理）包括：

- [Separator](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/separator/save.js)
- [Spacer](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/spacer/save.js)
- [Button](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#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/>

### 数据库中动态块的 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)块在数据库中[保存](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)块同样在数据库中[保存](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)。

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

## 其他资源

- [静态与动态区块：有何区别？](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/) | 开发者博客