#### 优化 render.php

最后一步是优化 `render.php` 文件。如果 `currentYear` 属性与 `fallbackCurrentYear` 属性相同，则无需动态生成区块内容。相关内容已保存在数据库中，并可通过 `$content` 变量在 `render.php` 文件中调用。

因此，请更新该文件，使其在 `currentYear` 与 `fallbackCurrentYear` 不匹配时渲染生成的内容。

```php
$current_year = date( "Y" );

// 确定要显示的内容
if ( isset( $attributes['fallbackCurrentYear'] ) && $attributes['fallbackCurrentYear'] === $current_year ) {

	// 当前年份与备用值相同，直接使用数据库中保存的区块内容（由 save.js 函数生成）
	$block_content = $content;
} else {

	// 当前年份与备用值不同，渲染更新后的区块内容
	if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) {
		$display_date = $attributes['startingYear'] . '–' . $current_year;
	} else {
		$display_date = $current_year;
	}

	$block_content = '<p ' . get_block_wrapper_attributes() . '>© ' . esc_html( $display_date ) . '</p>';
}

echo wp_kses_post( $block_content );
```

大功告成！现在您已拥有一个同时支持动态与静态渲染的区块。

## 收官之笔

恭喜您完成本教程并成功构建专属的版权日期区块！通过这段学习旅程，您已掌握 WordPress 区块开发的基础知识，现在可以开始创建自己的区块了。

本教程完整代码请参考 GitHub 上的 [区块开发示例库](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/copyright-date-block-09aac3)。

若您希望精进技能、挑战更复杂的项目或紧跟 WordPress 最新动态，以下资源将助您提升区块开发能力：

- [区块开发环境配置](https://developer.wordpress.org/block-editor/getting-started/devenv/)
- [区块开发基础指南](https://developer.wordpress.org/block-editor/getting-started/fundamentals/)
- [WordPress 开发者博客](https://developer.wordpress.org/news/)
- [区块开发示例库](https://github.com/WordPress/block-development-examples) | GitHub 代码库

请记住：每位专家都曾是初学者。持续学习、勇于实践，最重要的是，享受使用 WordPress 构建作品的乐趣！

# 教程：构建你的第一个区块

在本教程中，你将构建一个"版权日期区块"——这是一个基础但实用的区块，用于显示版权符号（©）、当前年份以及可选的起始年份。这类内容通常用于网站页脚。

本教程将引导你完成从使用 [`create-block`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) 包搭建区块插件脚手架到修改每个文件的完整流程。虽然具备WordPress开发经验会更有帮助，但这不是学习本教程的必要条件。

完成本指南后，你将清晰理解区块开发的基础知识，并掌握创建自定义WordPress区块的必要技能。

## 你将构建什么

快速预览即将构建的内容。

![即将构建的内容](https://developer.wordpress.org/files/2023/12/block-tutorial-1.png)

你也可以在 [WordPress Playground](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/WordPress/block-development-examples/trunk/plugins/copyright-date-block-09aac3/_playground/blueprint.json) 中体验完成后的项目，或使用[快速入门指南](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide/)将完整区块插件安装到本地WordPress环境中。

## 前置要求

完成本教程需要：

1. 代码编辑器
2. Node.js开发工具
3. 本地WordPress环境

若缺少其中任何一项，[区块开发环境](https://developer.wordpress.org/block-editor/getting-started/devenv/)文档将帮助你完成配置。准备就绪后请返回继续学习。

<div class="callout callout-info">
	本教程使用 <a href="https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/"><code>wp-env</code></a> 创建本地WordPress开发环境。当然你也可以使用任何满足上述前置要求的开发环境。
</div>

## 搭建区块脚手架

创建版权日期区块的第一步是使用 [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) 包搭建初始区块结构。

<div class="callout callout-info">
	建议先阅读<a href="https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/">create-block入门指南</a>了解该工具包的基本用法。
</div>

你可以在计算机的任意目录中运行 `create-block`，然后使用 `wp-env` 创建已安装并激活新区块插件的本地WordPress开发环境。

因此请选择存放区块插件的目录，也可新建名为"Block Tutorial"的文件夹。打开终端并进入该目录，然后运行以下命令。

<div class="callout callout-info">
	若未使用 <code>wp-env</code>，请通过终端进入本地WordPress安装目录的 <code>plugins/</code> 文件夹再运行以下命令。
</div>

```bash
npx @wordpress/create-block@latest copyright-date-block --variant=dynamic
cd copyright-date-block
```

执行命令后，插件文件夹中会出现名为 `copyright-date-block` 的新目录，其中包含定制区块所需的所有初始文件。

此命令同时建立了区块的基础结构，`copyright-date-block` 将作为区块在WordPress中的唯一标识符。

<div class="callout callout-info">
	你可能注意到命令中使用了 <code>--variant=dynamic</code> 标志，这表示要搭建动态渲染区块的脚手架。后续章节将介绍动态与静态渲染的区别，并为该区块添加静态渲染功能。
</div>

前往WordPress后台的插件页面确认插件已激活。接着新建页面或文章，确保可以插入版权日期区块。插入后的效果应如下图所示。

![编辑器中的区块脚手架](https://developer.wordpress.org/files/2023/12/block-tutorial-2.png)

### 更新 render.php 文件

虽然区块在编辑器中运行正常，但前端仍会显示默认的区块提示信息。要解决此问题，请打开 [`render.php`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#render-php) 文件，您将看到以下内容：

```php
<?php
...
?>
<p <?php echo get_block_wrapper_attributes(); ?>>
	<?php esc_html_e( 'Copyright Date Block – hello from a dynamic block!', 'copyright-date-block' ); ?>
</p>
```

与编辑器中的 `useBlockProps()` 函数类似，[`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) 会在[区块包装元素](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-server-side-render-markup)中输出所有必要的 CSS 类和样式。只需更新内容部分即可。

您可以使用 `date( "Y" )` 在 PHP 中获取当前年份，修改后的 `render.php` 文件应如下所示：

```php
<?php
...
?>
<p <?php echo get_block_wrapper_attributes(); ?>>© <?php echo date( "Y" ); ?></p>
```

保存文件后，请确认区块在编辑器和前端均能正确显示。

### 清理文件

当使用 `create-block` 工具包搭建区块时，可能会包含一些非必需文件。在本教程中，该区块并未使用样式表或前端 JavaScript。请通过以下操作清理插件的 `src/` 目录：

1. 在 `edit.js` 文件中移除导入 `editor.scss` 的代码行
2. 在 `index.js` 文件中移除导入 `style.scss` 的代码行
3. 删除 editor.scss、style.scss 和 view.js 文件

最后请确保所有修改均已保存，然后终止 `npm run start` 命令。运行 `npm run build` 以优化代码并完成生产环境准备。

至此您已构建了一个功能完整的 WordPress 区块，但我们的工作尚未结束。后续章节将继续添加功能并启用静态渲染。

## 添加区块属性

当前构建的版权日期区块仅显示当前年份，但如果需要同时显示起始年份该如何实现？

![即将构建的功能效果](https://developer.wordpress.org/files/2023/12/block-tutorial-1.png)

此功能需要用户在区块的某个位置输入起始年份，并且还应具备启用/禁用该功能的开关。

实现方式可以多样化，但都需要使用[区块属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/)。属性允许您为区块存储自定义数据，这些数据随后可用于修改区块的标记。

要实现起始年份功能，您需要定义一个属性存储起始年份，另一个属性用于控制是否显示起始年份。

### 更新 block.json 文件

区块属性通常在 [`block.json`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#data-storage-in-the-block-with-attributes) 文件中定义。请打开该文件，在 `example` 属性后添加以下配置段：

```json
"example": {},
"attributes": {
	"showStartingYear": {
		"type": "boolean"
	},
	"startingYear": {
		"type": "string"
	}
},
```

定义属性时必须指明 `type` 类型。本例中 `showStartingYear` 需存储布尔值，因此设为 `boolean` 类型；`startingYear` 则设为字符串类型。

保存文件后，即可开始编辑器的调整工作。

### 更新 edit.js 文件

打开 `edit.js` 文件，您需要完成两项任务：

- 添加用户界面控件，允许用户输入起始年份、切换功能开关，并将这些设置存储为属性
- 根据已定义的属性更新区块显示内容

### 更新 render.php 文件

虽然编辑器看起来很棒，但起始年份功能尚未添加到前端。让我们通过更新 `render.php` 文件来解决这个问题。

首先，添加一个名为 `$display_date` 的变量，并复制你在上面的 `Edit()` 函数中所做的操作。

这个变量应该显示 `startingYear` 属性和 `$current_year` 变量的值，用破折号分隔；如果 `showStartingYear` 属性为 `false`，则仅显示 `$current_year`。

<div class="callout callout-tip">
	<p>在 <code>render.php</code> 中暴露了三个变量，你可以使用它们来自定义块的输出：</p>
	<ul>
		<li><code>$attributes</code>（数组）：块的属性。</li>
		<li><code>$content</code>（字符串）：块的默认内容。</li>
		<li><code>$block</code>（WP_Block）：块的实例。</li>
	</ul>
</div>

代码应该看起来像这样。

```php
if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) {
	$display_date = $attributes['startingYear'] . '–' . $current_year;
} else {
	$display_date = $current_year;
}
```

接下来，你只需要更新块内容，使用 `$display_date` 而不是 `$current_year` 变量。

你最终的 `render.php` 文件应该如下所示。

```php
<?php
$current_year = date( "Y" );

if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) {
	$display_date = $attributes['startingYear'] . '–' . $current_year;
} else {
	$display_date = $current_year;
}
?>
<p <?php echo get_block_wrapper_attributes(); ?>>
    © <?php echo esc_html( $display_date ); ?>
</p>
```

保存文件，并确认正确的块内容现在出现在你网站的前端。

你现在已经成功构建了一个动态渲染的自定义块，它利用了块支持、WordPress 核心组件和自定义属性。在许多情况下，对于一个显示版权日期并具有一些额外功能的块来说，这已经足够了。

然而，在下一节中，你将向块添加静态渲染。这个练习将说明块数据是如何存储在 WordPress 中的，并在此插件意外禁用时提供回退方案。

## 添加静态渲染

一个块可以利用动态渲染、静态渲染，或者两者兼用。到目前为止，你构建的块是动态渲染的。它的块标记和关联的属性存储在数据库中，但其 HTML 输出并未存储。

静态渲染的块将始终将块标记、属性和输出存储在数据库中。块还可以在数据库中存储静态输出，同时在前端动态进一步增强，这是两种方法的结合。

如果你在编辑器中切换到代码编辑器，你将看到以下内容。

```html
<!-- wp:create-block/copyright-date-block {"showStartingYear":true,"startingYear":"2017"} /-->
```

将其与静态渲染的块（如段落块）进行比较。

```html
<!-- wp:paragraph -->
<p>这是一个测试。</p>
<!-- /wp:paragraph -->
```

段落的 HTML 存储在文章内容中，并保存在数据库中。

你可以在[基础文档](https://developer.wordpress.org/block-editor/getting-started/fundamentals/static-dynamic-rendering/)中了解更多关于动态和静态渲染的内容。虽然大多数块要么是动态渲染，要么是静态渲染，但你可以构建一个同时使用两种方法的块。

### 为什么要添加静态渲染？

当你向动态渲染的块添加静态渲染时，`render.php` 文件仍将控制前端的输出，但块的 HTML 内容将被保存在数据库中。这意味着，即使插件从网站中移除，内容仍然会保留。对于这个版权日期块，内容将恢复为一个自定义 HTML 块，你可以轻松地将其转换为段落块。

![当块类型不再存在时，编辑器中的错误消息](https://developer.wordpress.org/files/2023/12/block-tutorial-14.png)

虽然并非在所有情况下都需要，但向动态渲染的块添加静态渲染可以在插件意外禁用时提供有用的回退方案。

此外，考虑一种情况，块标记包含在块模式或主题模板中。如果用户安装该主题或使用该模式时未安装版权日期块，他们将收到一个块不可用的通知，但内容仍将显示。

添加静态渲染也是探索块内容在 WordPress 中如何存储和渲染的好方法。

## 文件结构审阅
在开始修改脚手架生成的区块之前，有必要先了解插件的文件结构。请用代码编辑器打开插件文件夹。

![构成区块插件的文件](https://developer.wordpress.org/files/2023/12/block-tutorial-3.png)

接下来查阅[区块文件结构](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/)文档，详细了解每个文件的功能。如果现在觉得内容太多不必担心，本教程将逐步讲解每个文件的使用方法。

<div class="callout callout-info">
	由于您创建的是动态区块，因此不会看到<code>save.js</code>文件。后续教程中将会把这个文件添加到插件以实现静态渲染，敬请关注。
</div>

## 初始设置

让我们从创建最简单的版权日期区块开始，这将是一个动态渲染的区块，仅显示版权符号（©）和当前年份。我们还将添加若干控件，允许用户修改字体大小和文本颜色。

在执行后续步骤前，请在插件目录的终端中运行 `npm run start` 命令。该命令将监听 `/src` 文件夹中每个文件的变更。每次保存文件时，区块的构建文件都会自动更新。

请参阅[区块编辑器的JavaScript开发指南](https://developer.wordpress.org/block-editor/getting-started/fundamentals/javascript-in-the-block-editor/)文档以了解更多信息。

### 更新block.json

打开 `/src` 文件夹中的 `block.json` 文件。

```json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "create-block/copyright-date-block",
	"version": "0.1.0",
	"title": "Copyright Date Block",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block scaffolded with Create Block tool.",
	"example": {},
	"supports": {
		"html": false
	},
	"textdomain": "copyright-date-block",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"render": "file:./render.php",
	"viewScript": "file:./view.js"
}
```

<div class="callout callout-info">
	建议查阅<a href="https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/">block.json文档</a>了解此文件的基本信息。
</div>

虽然脚手架工具已生成此文件，但需要根据版权日期区块的需求进行一些调整。

#### 修改区块标识

首先移除图标并添加更合适的描述（后续将添加自定义图标）：
1. 删除 `icon` 这一行
2. 将描述更新为“显示您网站的版权日期”
3. 保存文件

刷新编辑器后，您会看到区块不再显示笑脸图标，且描述已更新。

![更新信息后的编辑器区块显示](https://developer.wordpress.org/files/2023/12/block-tutorial-4.png)

#### 添加区块支持功能

接下来添加若干[区块支持功能](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)，以便用户控制区块的字体大小和文本颜色。

<div class="callout callout-tip">
	在构建自定义功能前，应优先使用原生区块支持功能。这种方式能为用户提供跨区块的一致编辑体验，且仅需少量代码即可让区块获得核心功能支持。
</div>

将 `block.json` 文件中的 [`supports`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#enable-ui-settings-panels-for-the-block-with-supports) 部分更新为：

```json
"supports": {
	"color": {
		"background": false,
		"text": true
	},
	"html": false,
	"typography": {
		"fontSize": true
	}
},
```

请注意当通过 `"text": true` 启用文本颜色支持时，背景颜色功能默认也会启用。您可以保留此设置，但本教程不需要该功能，因此可手动设置为 `"background": false`。

保存文件后在编辑器中选择区块，您将在设置面板中看到颜色和版式面板。尝试修改这些设置并观察效果。

![具备区块支持功能的编辑器区块显示](https://developer.wordpress.org/files/2023/12/block-tutorial-5.png)

### 添加保存功能

首先在 `src/` 文件夹中新建名为 `save.js` 的文件，并添加以下内容：

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

export default function save() {
	return (
		<p { ...useBlockProps.save() }>
			{ 'Copyright Date Block – hello from the saved content!' }
		</p>
	);
}
```

这段代码与原有的 `edit.js` 文件结构相似，更多细节可参考[区块包装器](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-save-components-markup)文档。

接着在 `index.js` 文件中导入这个 `save()` 函数，并为 `registerBlockType()` 函数添加 save 属性。以下是更新后文件的简化视图：

```js
import save from './save';

...

registerBlockType( metadata.name, {
	icon: calendarIcon,
	edit: Edit,
	save
} );
```

<div class="callout callout-tip">
	在定义对象属性时，若属性名与变量名相同，可使用属性简写语法。这就是为什么上面的代码使用 <code>save</code> 而非 <code>save: save</code>。
</div>

保存 `save.js` 和 `index.js` 文件后刷新编辑器，你将看到如下界面：

![编辑器中显示的区块验证错误信息](https://developer.wordpress.org/files/2023/12/block-tutorial-15.png)

不必担心，这个错误是预期内的。打开浏览器检查器，你会看到以下信息：

![控制台显示的区块验证错误信息](https://developer.wordpress.org/files/2023/12/block-tutorial-16.png)

出现区块验证错误是因为 `save()` 函数返回了区块内容，但由于之前保存的是动态区块，区块标记中并未存储HTML。当前标记结构如下所示：

```html
<!-- wp:create-block/copyright-date-block {"showStartingYear":true,"startingYear":"2017"} /-->
```

在后续步骤中更新 `save()` 函数时，你会多次遇到这类错误。只需点击“尝试恢复区块”并刷新页面即可。

完成区块恢复后，打开代码编辑器会看到标记已更新为：

```html
<!-- wp:create-block/copyright-date-block {"showStartingYear":true,"startingYear":"2017"} -->
<p class="wp-block-create-block-copyright-date-block">Copyright Date Block – hello from the saved content!</p>
<!-- /wp:create-block/copyright-date-block -->
```

使用静态渲染构建区块时经常会遇到验证错误，这属于正常现象。`save()` 函数的输出必须与文章内容中的HTML完全匹配，随着功能添加可能会出现不同步。只要在完整构建区块后没有验证错误，就表示大功告成。

### 更新 save.js

接下来更新 `save()` 函数的输出以显示正确内容。首先采用与 `edit.js` 相同的实现方式：

1. 为函数添加 `attributes` 参数
2. 定义 `showStartingYear` 和 `startingYear` 变量
3. 定义 `currentYear` 变量
4. 根据 `currentYear`、`showStartingYear` 和 `startingYear` 的值定义 `displayDate` 变量

最终代码应如下所示：

```js
export default function save( { attributes } ) {
	const { showStartingYear, startingYear } = attributes;
	const currentYear = new Date().getFullYear().toString();

	let displayDate;

	if ( showStartingYear && startingYear ) {
		displayDate = startingYear + '–' + currentYear;
	} else {
		displayDate = currentYear;
	}

	return (
		<p { ...useBlockProps.save() }>© { displayDate }</p>
	);
}
```

保存文件并刷新编辑器，点击“尝试恢复区块”后更新页面。在代码编辑器中查看，区块标记现在应显示为：

```html
<!-- wp:create-block/copyright-date-block {"showStartingYear":true,"startingYear":"2017"} -->
<p class="wp-block-create-block-copyright-date-block">© 2017–2023</p>
<!-- /wp:create-block/copyright-date-block -->
```

至此看似已完成所有工作——区块内容已以HTML形式存入数据库，前端输出也能动态渲染。但仍有几个问题需要解决。

试想这种情况：用户在2023年将区块添加到页面，然后在2024年重新编辑该页面。前端显示会正常更新，但在编辑器中会出现区块验证错误。因为 `save()` 函数识别当前是2024年，而数据库中存储的区块内容仍显示2023年。

我们将在下一节解决这个问题。

##### 切换控件

接下来，我们添加一个用于开启或关闭起始年份功能的切换开关。您可以通过设置 `showStartingYear` 属性的 `ToggleControl` 组件实现。该组件应包含：

1. 设置为“显示起始年份”的 `label` 属性
2. 设置为 `showStartingYear` 属性的 `checked` 属性
3. 在切换开关被勾选或取消时更新 `showStartingYear` 属性的 `onChange` 属性

您还可以更新“起始年份”文本输入框，使其仅在 `showStartingYear` 为 `true` 时显示，这可以通过 `&&` 逻辑运算符实现。

`Edit()` 函数应如下所示。

```js
export default function Edit( { attributes, setAttributes } ) {
	const { showStartingYear, startingYear } = attributes;
	const currentYear = new Date().getFullYear().toString();

	return (
		<>
			<InspectorControls>
				<PanelBody title={ __( '设置', 'copyright-date-block' ) }>
					<ToggleControl
						checked={ !! showStartingYear }
						label={ __(
							'显示起始年份',
							'copyright-date-block'
						) }
						onChange={ () =>
							setAttributes( {
								showStartingYear: ! showStartingYear,
							} )
						}
					/>
					{ showStartingYear && (
						<TextControl
							__nextHasNoMarginBottom
							__next40pxDefaultSize
							label={ __(
								'起始年份',
								'copyright-date-block'
							) }
							value={ startingYear || '' }
							onChange={ ( value ) =>
								setAttributes( { startingYear: value } )
							}
						/>
					) }
				</PanelBody>
			</InspectorControls>
			<p { ...useBlockProps() }>© { currentYear }</p>
		</>
	);
}
```

保存文件并刷新编辑器。确认点击切换开关会显示文本输入框，并且在更新页面时切换开关保持激活状态。

![在设置侧边栏中编辑新的“显示起始年份”切换开关的实时预览](https://developer.wordpress.org/files/2023/12/block-tutorial-12.gif)

#### 更新区块内容

到目前为止，您已经创建了用于添加起始年份并更新相关区块属性的用户界面。现在需要实际更新编辑器中的区块内容。

我们创建一个名为 `displayDate` 的新变量。当 `showStartingYear` 为 `true` 且用户提供了 `startingYear` 时，`displayDate` 应包含由破折号连接的 `startingYear` 和 `currentYear`。否则，仅显示 `currentYear`。

代码应如下所示。

```js
let displayDate;

if ( showStartingYear && startingYear ) {
	displayDate = startingYear + '–' + currentYear;
} else {
	displayDate = currentYear;
}
```

<div class="callout callout-tip">
	当您使用 <code>let</code> 声明变量时，表示该变量后续可能会被重新赋值。使用 <code>const</code> 声明变量则表示该变量永远不会改变。您也可以使用 <code>const</code> 重写此代码，这仅是个人偏好问题。
</div>

接下来，您只需更新区块内容，使用 `displayDate` 替代 `currentYear` 变量。

`Edit()` 函数应如下所示。

```js
export default function Edit( { attributes, setAttributes } ) {
	const { showStartingYear, startingYear } = attributes;
	const currentYear = new Date().getFullYear().toString();

	let displayDate;

	if ( showStartingYear && startingYear ) {
			displayDate = startingYear + '–' + currentYear;
	} else {
		displayDate = currentYear;
	}

	return (
		<>
			<InspectorControls>
				<PanelBody title={ __( '设置', 'copyright-date-block' ) }>
					<ToggleControl
						checked={ !! showStartingYear }
						label={ __(
							'显示起始年份',
							'copyright-date-block'
						) }
						onChange={ () =>
							setAttributes( {
								showStartingYear: ! showStartingYear,
							} )
						}
					/>
					{ showStartingYear && (
						<TextControl
							label={ __(
								'起始年份',
								'copyright-date-block'
							) }
							value={ startingYear || '' }
							onChange={ ( value ) =>
								setAttributes( { startingYear: value } )
							}
						/>
					) }
				</PanelBody>
			</InspectorControls>
			<p { ...useBlockProps() }>© { displayDate }</p>
		</>
	);
}
```

保存文件并刷新编辑器。确认当您在设置面板中进行更改时，区块内容会正确更新。

![设置侧边栏中新字段更新区块内容的实时预览](https://developer.wordpress.org/files/2023/12/block-tutorial-13.gif)

### 处理静态渲染区块中的动态内容

通常来说，您需要避免在静态渲染区块中使用动态内容。这也是为什么在提及动态渲染时会使用"动态"这个术语的原因之一。

不过在本教程中，您将结合使用两种渲染方法，只需要添加少量代码就能在年份变更时避免区块验证错误。

问题的根源在于`currentYear`变量是在`save()`函数中动态设置的。实际上，这应该是函数内的静态变量，可以通过添加属性来解决这个问题。

#### 添加新属性

打开`block.json`文件，添加一个名为`fallbackCurrentYear`、类型为`string`的新属性。此时文件的`attributes`部分应如下所示：

```json
"attributes": {
    "fallbackCurrentYear": {
		"type": "string"
    },
    "showStartingYear": {
		"type": "boolean"
    },
    "startingYear": {
		"type": "string"
	}
},
```

接下来打开`save.js`文件，使用新的`fallbackCurrentYear`属性替代`currentYear`。更新后的`save()`函数应如下所示：

```js
export default function save( { attributes } ) {
	const { fallbackCurrentYear, showStartingYear, startingYear } = attributes;

	let displayDate;

	if ( showStartingYear && startingYear ) {
		displayDate = startingYear + '–' + fallbackCurrentYear;
	} else {
		displayDate = fallbackCurrentYear;
	}

	return (
		<p { ...useBlockProps.save() }>© { displayDate }</p>
	);
}
```

现在，如果`fallbackCurrentYear`未定义会发生什么？

之前`currentYear`是在函数内定义的，因此即使`showStartingYear`和`startingYear`未定义，`save()`函数也始终有内容可以返回。

与其只返回版权符号，不如添加一个条件判断：如果`fallbackCurrentYear`未设置，则返回`null`。通常来说，在数据库中保存空内容比保存不完整的数据更好。

最终的`save()`函数应如下所示：

```js
export default function save( { attributes } ) {
	const { fallbackCurrentYear, showStartingYear, startingYear } = attributes;

	if ( ! fallbackCurrentYear ) {
		return null;
	}

	let displayDate;

	if ( showStartingYear && startingYear ) {
		displayDate = startingYear + '–' + fallbackCurrentYear;
	} else {
		displayDate = fallbackCurrentYear;
	}

	return (
		<p { ...useBlockProps.save() }>© { displayDate }</p>
	);
}
```

保存`block.json`和`save.js`文件，您无需再进行其他修改。

#### 在edit.js中设置属性

既然`save()`函数现在使用新的`fallbackCurrentYear`，就需要在某个地方设置这个值。让我们使用`Edit()`函数来实现。

打开`edit.js`文件，首先在`Edit()`函数顶部与其他属性一起定义`fallbackCurrentYear`变量。接着检查函数中的现有逻辑。

当区块在编辑器中加载时，会定义`currentYear`变量。函数随后使用该变量来设置区块内容。

现在，让我们在区块加载时，如果`fallbackCurrentYear`属性尚未设置，就将其设置为`currentYear`的值：

```js
if ( currentYear !== fallbackCurrentYear ) {
	setAttributes( { fallbackCurrentYear: currentYear } );
}
```

这样虽然可行，但可以通过确保此代码仅在区块初始化时运行一次来进行优化。为此，您可以使用React的[`useEffect`](https://react.dev/reference/react/useEffect)钩子。有关如何使用此钩子的更多信息，请参阅React文档。

首先通过以下代码导入`useEffect`：

```js
import { useEffect } from 'react';
```

然后将上面的`setAttribute()`代码包装在`useEffect`中，并将这段代码放在`Edit()`函数的`currentYear`定义之后。最终结果应如下所示：

```js
export default function Edit( { attributes, setAttributes } ) {
	const { fallbackCurrentYear, showStartingYear, startingYear } = attributes;

	// 获取当前年份并确保其为字符串类型
	const currentYear = new Date().getFullYear().toString();

	// 当区块加载时，如果fallbackCurrentYear尚未设置，
	// 则将其属性设置为当前年份
	useEffect( () => {
		if ( currentYear !== fallbackCurrentYear ) {
			setAttributes( { fallbackCurrentYear: currentYear } );
		}
	}, [ currentYear, fallbackCurrentYear, setAttributes ] );

	...
```

当区块在编辑器中初始化时，`fallbackCurrentYear`属性将立即被设置。这个值随后可供`save()`函数使用，正确的区块内容将显示且不会出现区块验证错误。

唯一需要注意的是年份变更的情况。如果版权日期区块在2023年添加到页面，然后在2024年进行编辑，`fallbackCurrentYear`属性将不再等于`currentYear`，该属性将自动更新为`2024`。这将更新`save()`函数返回的HTML。

虽然您不会遇到任何区块验证错误，但编辑器会检测到页面已发生更改，并提示您进行更新。

#### 添加用户界面

在本教程的前面部分，您添加了区块支持功能，这些功能会在区块的设置侧边栏中自动创建颜色和排版面板。您可以使用 `InspectorControls` 组件创建自定义面板。

##### 检查器控件

`InspectorControls` 属于 [`@wordpress/block-editor`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/) 包，因此您可以通过在第 14 行添加组件名称将其导入到 `edit.js` 文件中。结果应如下所示：

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

接下来，更新 Edit 函数，使其返回当前区块内容和一个包含“测试”文本的 `InspectorControls` 组件。您可以将所有内容包装在 [Fragment](https://react.dev/reference/react/Fragment) (`<></>`) 中，以确保 JSX 语法正确。结果应如下所示：

```js
export default function Edit() {
	const currentYear = new Date().getFullYear().toString();

	return (
		<>
			<InspectorControls>
				测试
			</InspectorControls>
			<p { ...useBlockProps() }>© { currentYear }</p>
		</>
	);
}
```

保存文件并刷新编辑器。选择区块时，您应在设置侧边栏中看到“测试”消息。

![设置侧边栏现在显示消息](https://developer.wordpress.org/files/2023/12/block-tutorial-9.png)

##### 组件和面板

现在，让我们使用更多核心组件来添加自定义面板和起始年份功能的用户界面。您需要从 [`@wordpress/components`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-components/) 包中导入 [`PanelBody`](https://developer.wordpress.org/block-editor/reference-guides/components/panel/#panelbody)、[`TextControl`](https://developer.wordpress.org/block-editor/reference-guides/components/text-control/) 和 [`ToggleControl`](https://developer.wordpress.org/block-editor/reference-guides/components/toggle-control/)。

在 `edit.js` 文件的其他导入下方添加以下行：

```js
import { PanelBody, TextControl, ToggleControl } from '@wordpress/components';
```

然后将“测试”消息包装在 `PanelBody` 组件中，并将 `title` 参数设置为“设置”。有关其他参数选项，请参阅[组件文档](https://developer.wordpress.org/block-editor/reference-guides/components/panel/#panelbody)。

```js
export default function Edit() {
	const currentYear = new Date().getFullYear().toString();

	return (
		<>
			<InspectorControls>
				<PanelBody title={ __( '设置', 'copyright-date-block' ) }>
					测试
				</PanelBody>
			</InspectorControls>
			<p { ...useBlockProps() }>© { currentYear }</p>
		</>
	);
}
```

保存文件并刷新编辑器。您现在应该可以看到新的设置面板。

![设置侧边栏现在显示自定义面板](https://developer.wordpress.org/files/2023/12/block-tutorial-10.png)

##### 文本控件

下一步是将“测试”消息替换为 `TextControl` 组件，该组件允许用户设置 `startingYear` 属性。但在执行此操作之前，您必须在 `Edit()` 函数中包含两个参数：

- `attributes` 是一个包含区块所有属性的对象。
- `setAttributes` 是一个允许您更新属性值的函数。

包含这些参数后，您可以获取 `showStartingYear` 和 `startingYear` 属性。

更新 `Edit()` 函数的开头部分，使其如下所示：

```js
export default function Edit( { attributes, setAttributes } ) {
	const { showStartingYear, startingYear } = attributes;
	...
```

<div class="callout callout-tip">
	要查看与版权日期区块关联的所有属性，请在 <code>Edit()</code> 函数的开头添加 <code>console.log( attributes );</code>。这在构建和测试自定义区块时非常有用。
</div>

现在，您可以删除“测试”消息并添加 `TextControl`。它应包括：

1. 一个设置为“起始年份”的 `label` 属性。
2. 一个设置为属性 `startingYear` 的 `value` 属性。
3. 一个 `onChange` 属性，用于在值更改时更新 `startingYear` 属性。

将所有内容整合后，`Edit()` 函数应如下所示：

```js
export default function Edit( { attributes, setAttributes } ) {
	const { showStartingYear, startingYear } = attributes;
	const currentYear = new Date().getFullYear().toString();

	return (
		<>
			<InspectorControls>
				<PanelBody title={ __( '设置', 'copyright-date-block' ) }>
					<TextControl
					    __nextHasNoMarginBottom
						__next40pxDefaultSize
						label={ __(
							'起始年份',
							'copyright-date-block'
						) }
						value={ startingYear || '' }
						onChange={ ( value ) =>
							setAttributes( { startingYear: value } )
						}
					/>
				</PanelBody>
			</InspectorControls>
			<p { ...useBlockProps() }>© { currentYear }</p>
		</>
	);
}
```

<div class="callout callout-tip">
	您可能已经注意到 <code>value</code> 属性的值为 <code>startingYear || ''</code>。符号 <code>||</code> 称为 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Logical_OR">逻辑或</a>（逻辑析取）运算符。这可以防止当 <code>startingYear</code> 为空时在 React 中出现警告。详情请参阅 <a href="https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components">受控和非受控组件</a>。
</div>

保存文件并刷新编辑器。确认设置面板中现在存在一个文本字段。添加一个起始年份，并确认在更新页面时该值已保存。

![在设置侧边栏中编辑新的起始年份字段的实时预览](https://developer.wordpress.org/files/2023/12/block-tutorial-11.gif)

#### 移除不必要的代码

为简化操作，版权日期区块的样式将完全由颜色与排版区块支持功能控制。该区块也不包含任何前端JavaScript代码。因此，您无需在`block.json`文件中指定样式表或`viewScript`参数。

1. 删除`editorStyle`配置行
2. 删除`style`配置行
3. 删除`viewScript`配置行
4. 保存文件

刷新编辑器后，您将看到区块样式已与当前主题保持一致。

![编辑器中去掉默认样式的区块效果](https://developer.wordpress.org/files/2023/12/block-tutorial-6.png)

#### 完整配置汇总

最终完成的`block.json`文件应如下所示：

```json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "create-block/copyright-date-block",
	"version": "0.1.0",
	"title": "版权日期区块",
	"category": "widgets",
	"description": "显示站点版权日期",
	"example": {},
	"supports": {
		"color": {
			"background": false,
			"text": true
		},
		"html": false,
		"typography": {
			"fontSize": true
		}
	},
	"textdomain": "copyright-date-block",
	"editorScript": "file:./index.js",
	"render": "file:./render.php"
}
```

### 更新index.js文件

在开始构建区块功能之前，我们先进行清理工作并为区块添加自定义图标。

打开[`index.js`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#index-js)文件，这是区块的主JavaScript文件，用于在客户端注册区块。您可以通过[区块注册文档](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/)深入了解客户端与服务端注册机制。

首先查看[`registerBlockType`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/)函数。该函数接收从导入的`block.json`文件中获取的区块名称，以及区块配置对象。

```js
import Edit from './edit';
import metadata from './block.json';

registerBlockType( metadata.name, {
	edit: Edit,
} );
```

默认配置对象仅包含`edit`属性，但您还可以添加包括`icon`在内的更多属性。虽然大部分属性已在`block.json`中定义，但若需使用自定义SVG图标，则需要在此处专门指定。

#### 添加自定义图标

从[Gutenberg组件库](https://wordpress.github.io/gutenberg/?path=/story/icons-icon--library)获取日历图标，按如下方式将SVG代码添加到函数中：

```js
const calendarIcon = (
	<svg
		viewBox="0 0 24 24"
		xmlns="http://www.w3.org/2000/svg"
		aria-hidden="true"
		focusable="false"
	>
		<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm.5 16c0 .3-.2.5-.5.5H5c-.3 0-.5-.2-.5-.5V7h15v12zM9 10H7v2h2v-2zm0 4H7v2h2v-2zm4-4h-2v2h2v-2zm4 0h-2v2h2v-2zm-4 4h-2v2h2v-2zm4 0h-2v2h2v-2z"></path>
	</svg>
);

registerBlockType( metadata.name, {
	icon: calendarIcon,
	edit: Edit
} );
```

<div class="callout callout-tip">
	所有区块图标尺寸应为24x24像素。请注意上方代码中的<code>viewBox</code>参数。
</div>

保存`index.js`文件并刷新编辑器，您将看到默认图标已替换为日历图标。

![编辑器中使用自定义图标的区块](https://developer.wordpress.org/files/2023/12/block-tutorial-7.png)

至此，区块的图标与描述信息已正确配置，区块支持功能也允许您调整字体大小和文本颜色。接下来将进入区块实际功能的开发阶段。

### 更新edit.js文件

[`edit.js`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#edit-js)文件控制着区块在编辑器中的功能与显示效果。当前用户会看到“Copyright Date Block – hello from the editor!”提示信息，现在我们来修改这一内容。

打开文件可见`Edit()`函数返回包含默认信息的段落标签：

```js
export default function Edit() {
	return (
		<p { ...useBlockProps() }>
			{ __(
				'Copyright Date Block – hello from the editor!',
				'copyright-date-block-demo'
			) }
		</p>
	);
}
```

其实际逻辑比表面看起来更简洁：

- [`useBlockProps()`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#block-wrapper-props)用于输出编辑器所需的[区块包装器](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-edit-components-markup)中的所有CSS类与样式，包含先前添加的区块支持功能提供的样式
- [`__()`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/)用于实现文本字符串的国际化

<div class="callout callout-info">
	建议查阅<a href="https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/">区块包装器文档</a>，了解如何确保区块标记包装器具备正确属性的入门指南。
</div>

需要明确的是，本区块的核心功能是显示版权符号(©)与当前年份。首先需要以字符串格式获取当前年份，可通过以下代码实现：

```js
const currentYear = new Date().getFullYear().toString();
```

接着更新函数以显示正确信息：

```js
export default function Edit() {
	const currentYear = new Date().getFullYear().toString();

	return (
		<p { ...useBlockProps() }>© { currentYear }</p>
	);
}
```

保存`edit.js`文件并刷新编辑器，您现在将看到版权符号(©)后接当前年份的显示效果。

![编辑器中正确显示内容的区块](https://developer.wordpress.org/files/2023/12/block-tutorial-8.png)