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

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

本教程将引导你完成完整流程，从使用 [`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/">Get started with create-block</a> 文档，了解如何使用此包的介绍。
</div>

你可以在计算机上的几乎任何目录（文件夹）中使用 `create-block`，然后使用 `wp-env` 创建一个本地 WordPress 开发环境，并安装和激活你的新区块插件。

因此，选择一个目录来放置区块插件，或者可选地创建一个名为 "Block Tutorial" 的新文件夹。打开终端并 `cd` 到此目录。然后运行以下命令。

<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> 标志。这告诉 <code>create-block</code> 你想要搭建一个动态渲染的区块。在本教程后面，你将了解动态和静态渲染，并为此区块添加静态渲染。
</div>

导航到 WordPress 管理后台的插件页面，确认插件已激活。然后，创建一个新页面或文章，并确保可以插入版权日期区块。插入后应如下图所示。

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

## 查看文件
在开始修改脚手架生成的区块之前，先了解插件的文件结构很重要。请在代码编辑器中打开插件文件夹。

![构成区块插件的文件](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>

由于此脚手架过程创建了此文件，因此需要对其进行一些更新以满足 Copyright Date Block 的需求。

#### 修改区块标识

首先移除图标并添加更合适的描述。稍后您将添加自定义图标。

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)

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

为简化起见，版权日期区块的样式将完全由颜色和排版区块支持控制。此区块也没有任何前端 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": "Copyright Date Block",
	"category": "widgets",
	"description": "Display your site's copyright date.",
	"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 Storybook](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">
	所有区块图标应为 24 像素正方形。请注意上面的 <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)

### 更新 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/)。属性允许您为区块存储自定义数据，这些数据可用于修改区块的标记。

为实现起始年份功能，您需要一个属性来存储起始年份，另一个属性则用于告知 WordPress 是否应显示起始年份。

### 更新 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` 应为 true 或 false，因此其类型设为 `boolean`。而 `startingYear` 只是一个字符串。

保存文件后，您现在可以继续处理编辑器部分。

### 更新 edit.js 文件

打开 `edit.js` 文件。你需要完成两项任务。

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

#### 添加用户界面

在本教程的前面部分，您添加了区块支持，这些支持会在区块的设置侧边栏中自动创建颜色和排版面板。您可以使用 `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 函数，使其返回当前块内容和一个包含文本 "Testing" 的 `InspectorControls` 组件。你可以将所有内容包裹在一个 [Fragment](https://react.dev/reference/react/Fragment) (`<></>`) 中，以确保正确的 JSX 语法。结果应如下所示。

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

	return (
		<>
			<InspectorControls>
				Testing
			</InspectorControls>
			<p { ...useBlockProps() }>© { currentYear }</p>
		</>
	);
}
```
保存文件并刷新编辑器。选择块时，你应该会在设置侧边栏中看到 "Testing" 消息。

![设置侧边栏现在显示该消息](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';
```

然后将 "Testing" 消息包裹在 `PanelBody` 组件中，并将 `title` 参数设置为 "Settings"。更多参数选项请参考[组件文档](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={ __( 'Settings', 'copyright-date-block' ) }>
					Testing
				</PanelBody>
			</InspectorControls>
			<p { ...useBlockProps() }>© { currentYear }</p>
		</>
	);
}
```

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

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

##### 文本控件

下一步是将“Testing”消息替换为允许用户设置 `startingYear` 属性的 `TextControl` 组件。但在执行此操作之前，必须在 `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>

现在，你可以移除“Testing”消息并添加 `TextControl`。它应包含：

1. 设置为“Starting year”的 `label` 属性
2. 设置为属性 `startingYear` 的 `value` 属性
3. 一个在值更改时更新 `startingYear` 属性的 `onChange` 属性

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

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

	return (
		<>
			<InspectorControls>
				<PanelBody title={ __( 'Settings', 'copyright-date-block' ) }>
					<TextControl
						__next40pxDefaultSize
						label={ __(
							'Starting year',
							'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/en-US/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)

##### 切换控制

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

1. 一个 `label` 属性，设置为 "显示起始年份"
2. 一个 `checked` 属性，设置为属性 `showStartingYear`
3. 一个 `onChange` 属性，用于在切换开关被选中或取消选中时更新 `showStartingYear` 属性

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

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

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

	return (
		<>
			<InspectorControls>
				<PanelBody title={ __( 'Settings', 'copyright-date-block' ) }>
					<ToggleControl
						checked={ !! showStartingYear }
						label={ __(
							'Show starting year',
							'copyright-date-block'
						) }
						onChange={ () =>
							setAttributes( {
								showStartingYear: ! showStartingYear,
							} )
						}
					/>
					{ showStartingYear && (
						<TextControl
							__next40pxDefaultSize
							label={ __(
								'Starting year',
								'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={ __( 'Settings', 'copyright-date-block' ) }>
					<ToggleControl
						checked={ !! showStartingYear }
						label={ __(
							'Show starting year',
							'copyright-date-block'
						) }
						onChange={ () =>
							setAttributes( {
								showStartingYear: ! showStartingYear,
							} )
						}
					/>
					{ showStartingYear && (
						<TextControl
							label={ __(
								'Starting year',
								'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)

### 更新 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>This is a test.</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 中如何存储和渲染的良好方式。

### 添加保存函数

首先在 `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 年。

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

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

通常，您应避免在静态渲染区块中使用动态内容。这也是为什么在提及动态渲染时会使用“动态”一词的部分原因。

尽管如此，在本教程中，您将结合使用两种渲染方法，并且只需添加少量代码，即可在年份变化时避免任何区块验证错误。

问题的根源在于 `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`。通常，在数据库中不保存 HTML 比保存不完整的数据更好。

最终的 `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 } );
}
```

这可以工作，但可以通过确保此代码仅在区块初始化时运行一次来改进。为此，你可以使用 [`useEffect`](https://react.dev/reference/react/useEffect) React 钩子。有关如何使用此钩子的更多信息，请参阅 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。

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

#### Optimizing render.php

The final step is to optimize the `render.php` file. If the `currentYear` and the `fallbackCurrentYear` attribute are the same, then there is no need to dynamically create the block content. It is already saved in the database and is available in the  `render.php` file via the `$content` variable.

Therefore, update the file to render the generated content if `currentYear` and `fallbackCurrentYear` do not match.

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

// Determine which content to display.
if ( isset( $attributes['fallbackCurrentYear'] ) && $attributes['fallbackCurrentYear'] === $current_year ) {

	// The current year is the same as the fallback, so use the block content saved in the database (by the save.js function).
	$block_content = $content;
} else {

	// The current year is different from the fallback, so render the updated block content.
	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 );
```

That's it! You now have a block that utilizes both dynamic and static rendering.

## 总结

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

作为最终参考，本教程的完整代码可在 GitHub 上的 [Block Development Examples](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 构建的乐趣。