文章元数据(post meta)是 WordPress 开发中常用的数据存储方式。在 WordPress 5.0 引入区块编辑器之前,开发者通常在文章编辑页加入元数据框,让用户输入与某一篇文章关联的数据,再在主题或插件模板中读取。
自定义区块和区块样板提供了另一种选择:把数据作为区块属性,随文章正文保存。究竟选择元数据还是文章正文,取决于具体需求;通常可以先看数据是否需要作为查询条件,或者是否会显示在所属文章类型的单篇模板之外。本教程通过一个具体例子建立基本做法。
需求:产品评价与作者信息
假设客户的网站已经有一个名为 Product 的自定义文章类型。他们希望给产品填写评价,并在首页显示最近五个有评价的产品。评价还要记录作者姓名和网站 URL,但这两项仅在产品的单篇模板中显示。
确定数据的存放位置
需要保存三项数据:评价正文、作者姓名、作者网站 URL。由于首页需要查询有评价的产品,评价正文放入文章元数据;作者姓名和 URL 不参与查询,只在单篇页面出现,可以作为区块属性随文章正文保存。
Quote 或 Pullquote 区块很适合引用文字,也可以通过 区块过滤器扩展字段。不过,它们把引用内容保存在文章正文中,不能直接满足本例把评价保存到 post meta 的需求。此外,前端需要用 get_post_meta() 从 WordPress 读取评价,因此这里采用自定义动态区块。
注册文章类型和元数据
这个示例假设现有 Product 文章类型已按下面的片段注册:
register_post_type(
'product',
array(
'labels' => array(
'name' => __( 'Products', 'tutorial' ),
'singular_name' => __( 'Product', 'tutorial' ),
),
'public' => true,
'has_archive' => true,
'show_in_rest' => true,
'supports' => array(
'title',
'editor',
'thumbnail',
'excerpt',
'custom-fields',
),
)
);
要在文章编辑器中访问元数据,必须通过 REST API 暴露它。使用 register_post_meta 注册 product 类型的 testimonial,把 show_in_rest 设为 true。sanitize_callback 使用 wp_kses_post,在保存到数据库前按允许的文章 HTML 规则清理内容:
register_post_meta(
'product',
'testimonial',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'sanitize_callback' => 'wp_kses_post',
)
);
这里同时注册了字符串类型和单值字段;文章类型的 supports 中包含 custom-fields。
用脚手架创建动态区块
@wordpress/create-block 可以生成注册区块的插件。在主题内注册区块也是可行的,有些项目更适合这样做;本教程把代码放入插件,以减少额外设置。
以下是原教程的脚手架命令。在自己的开发环境的 wp-content/plugins 目录中执行它,会生成一个包含动态区块的插件:
npx @wordpress/create-block post-meta-testimonial --variant dynamic --namespace tutorial --category text --short-description "A block quote style testimonial that saves to post meta."
--variant dynamic 指定动态区块:前端由 PHP 渲染,不把完整的前端展示标记直接存入文章正文。其他参数设定命名空间、区块分类和简短描述。详细选项见 create-block 官方文档。
若向现有插件添加区块,也可以用 --no-plugin 仅生成区块文件;这种情况下应在插件的 src 目录中运行命令。
上面的名称是 post-meta-testimonial,因此应在生成的实际插件目录中工作,再在开发站点激活该插件。原文后续写成 post-meta-tutorial,与命令中的名称不一致,这里按命令名说明。
进入 wp-content/plugins/post-meta-testimonial 后运行 npm run start 启动构建监听。这个命令由 @wordpress/scripts 提供,监听 src 文件的修改并重新编译。此时可以在编辑器中插入刚生成的区块。
这是一篇 2023 年教程的实现路径。下面保留其 apiVersion: 2 等示例设置;脚手架输出可能随版本变化,本次没有执行安装、构建或站点操作。
修改 block.json
脚手架准备好后,先定义两个字符串属性:authorName 和 authorURL。它们不设默认值,分别保存作者姓名和网站 URL:
"attributes": {
"authorName": {
"type": "string"
},
"authorURL": {
"type": "string"
}
},
区块属性的详细定义见 官方文档。曾经可以通过 block.json 把属性直接连接到文章元数据,但这种方式已被弃用,不应在这个实现中继续使用。
区块还需要知道当前文章 ID 和类型。usesContext 允许区块接收祖先区块或编辑器提供的上下文;加入 postId 和 postType:
"usesContext": ["postId", "postType"]
相关概念见 区块上下文文档。接着把图标改成更贴合评价用途的引用图标:
"icon": "format-quote"
同一篇文章共享一个 testimonial 字段,所以不应允许插入多个这样的编辑区块。在 supports 中设置 multiple: false:
"supports": {
"html": false,
"multiple": false
},
可以用 example 定义区块插入器中的预览数据:
"example": {
"attributes": {
"authorName": "WordPress",
"authorURL": "developer.wordpress.org/news"
}
},
其他属性可以按实际需求调整。完成后,block.json 的内容类似下面这样:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "tutorial/post-meta-testimonial",
"version": "0.1.0",
"title": "Post Meta Testimonial",
"category": "text",
"icon": "format-quote",
"description": "A block quote style testimonial that saves to post meta.",
"supports": {
"html": false,
"multiple": false
},
"example": {
"attributes": {
"authorName": "WordPress",
"authorURL": "developer.wordpress.org/news"
}
},
"attributes": {
"authorName": {
"type": "string"
},
"authorURL": {
"type": "string"
}
},
"usesContext": [ "postId", "postType" ],
"textdomain": "post-meta-testimonial",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"render": "file:./render.php"
}
在 edit.js 中构建编辑界面
接下来修改编辑器中的区块行为。打开脚手架生成的 edit.js。原始文件通常有较多注释,下面保留教程简化后的版本:
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import './editor.scss';
/**
* The edit function describes the structure of your block in the context of the
* editor. This represents what the editor will render when the block is used.
*
* @return {WPElement} Element to render.
*/
export default function Edit() {
return (
<p { ...useBlockProps() }>
{ __(
'Post Meta Testimonial – hello from the editor!',
'post-meta-testimonial'
) }
</p>
);
}
调整标记和样式
把 JSX 改成一个 blockquote,内部包含评价段落 p 和署名 cite。署名中用两个 span 分别显示作者姓名和网站,网站文字包在 a 中。前端也将使用相同结构:
export default function Edit() {
return (
<blockquote { ...useBlockProps() }>
<p>Testimonial will go here</p>
<cite>
<span>Author Name</span>
<br />
<span>
<a href="#">Author URL</a>
</span>
</cite>
</blockquote>
);
}
在 style.scss 中加入下面的样式,让区块呈现引用文字的效果:
.wp-block-tutorial-post-meta-testimonial {
border-left: 0.25em solid;
margin: 0 0 1.75em;
padding-left: 1em;
border-width: 1px;
}
这段样式最后的 border-width: 1px 会覆盖前面声明的左边框宽度;代码按原文保留。
加入组件和交互
Edit 接收一个 props 对象。通过解构取得 attributes、setAttributes,以及 context 中的 postType 和 postId:
export default function Edit( {
attributes,
setAttributes,
context: { postType, postId },
} ) {
return (
<blockquote { ...useBlockProps() }>
<p>Testimonial will go here</p>
<cite>
<span>Author Name</span>
<br />
<span>
<a href="#">Author URL</a>
</span>
</cite>
</blockquote>
);
}
现在需要让用户输入数据。TextControl 等输入组件也可以使用;这里选用 RichText,提供所见即所得的编辑体验和基本格式控制。从 @wordpress/block-editor 导入它:
import { useBlockProps, RichText } from '@wordpress/block-editor';
本例使用 RichText 的六种属性:
tagName:可编辑元素的标签名。value:字段当前内容。onChange:内容改变时调用的函数。placeholder:字段为空时显示的提示文字。allowedFormats:允许的格式,例如粗体和斜体。disableLineBreaks:是否禁止换行。
需要三个 RichText:一个编辑评价正文,另外两个编辑作者属性。先更新 cite 中的内容:
export default function Edit( {
attributes: { authorName, authorURL },
setAttributes,
context: { postType, postId },
} ) {
return (
<blockquote { ...useBlockProps() }>
<p>Testimonial goes here</p>
<cite>
<RichText
tagName="span"
placeholder={ __( 'Author name', 'tutorial' ) }
allowedFormats={ [] }
disableLineBreaks
value={ authorName }
onChange={ ( newAuthorName ) =>
setAttributes( { authorName: newAuthorName } )
}
/>
<br />
<span>
<RichText
tagName="a"
placeholder={ __( 'Author URL', 'tutorial' ) }
allowedFormats={ [] }
disableLineBreaks
value={ authorURL }
onChange={ ( newAuthorURL ) =>
setAttributes( { authorURL: newAuthorURL } )
}
/>
</span>
</cite>
</blockquote>
);
}
姓名和 URL 的 onChange 用 setAttributes 更新对应属性。这里的 allowedFormats={ [] } 不允许格式,disableLineBreaks 禁止换行。更多行为可查看 RichText 源码。
读取和更新文章元数据
要给评价正文的 RichText 提供数据,先从 @wordpress/core-data 导入 useEntityProp:
import { useEntityProp } from '@wordpress/core-data';
这个 hook 接收四个参数:实体种类 kind、实体名称 name、属性名 prop,以及可显式指定实体 ID 的 id。
本例使用 'postType'、'product'、'meta' 和上下文提供的 postId。显式传入文章 ID,能让它读取区块所指向文章的元数据;区块可能位于文章正文、站点编辑器,或 Query Loop 的模板中。
useEntityProp 返回数组:第一项是读取到的数据,第二项是更新数据的函数。与 useState 的形式相似,可以解构为 meta 和 updateMeta。meta 包含该文章类型已注册的元数据,再从中取出 testimonial。
这里的字段只注册给 Product,并且代码把实体名称写死为 'product'。把区块用于其他文章类型时会出现问题;这些示例不包含跨类型使用的防护逻辑。
export default function Edit( {
attributes: { authorName, authorURL },
setAttributes,
context: { postType, postId },
} ) {
const [ meta, updateMeta ] = useEntityProp(
'postType',
'product',
'meta',
postId
);
const { testimonial } = meta;
return (
<blockquote { ...useBlockProps() }>
<p>Testimonial goes here</p>
<cite>
<RichText
tagName="span"
placeholder={ __( 'Author name', 'tutorial' ) }
allowedFormats={ [] }
disableLineBreaks
value={ authorName }
onChange={ ( newAuthorName ) =>
setAttributes( { authorName: newAuthorName } )
}
/>
<br />
<span>
<RichText
tagName="a"
placeholder={ __( 'Author URL', 'tutorial' ) }
allowedFormats={ [] }
disableLineBreaks
value={ authorURL }
onChange={ ( newAuthorURL ) =>
setAttributes( { authorURL: newAuthorURL } )
}
/>
</span>
</cite>
</blockquote>
);
}
取得字段后,把评价正文的 RichText 的 value 设为 testimonial,在 onChange 中调用 updateMeta。该函数接收元数据对象;为了不覆盖其他字段,先展开 ...meta,再设置新的 testimonial 值:
export default function Edit( {
attributes: { authorName, authorURL },
setAttributes,
context: { postType, postId },
} ) {
const [ meta, updateMeta ] = useEntityProp(
'postType',
'product',
'meta',
postId
);
const { testimonial } = meta;
return (
<blockquote { ...useBlockProps() }>
<RichText
placeholder={ __( 'Testimonial goes here', 'tutorial' ) }
tagName="p"
value={ testimonial }
onChange={ ( newTestimonialContent ) =>
updateMeta( {
...meta,
testimonial: newTestimonialContent,
} )
}
/>
<cite>
<RichText
tagName="span"
placeholder={ __( 'Author name', 'tutorial' ) }
allowedFormats={ [] }
disableLineBreaks
value={ authorName }
onChange={ ( newAuthorName ) =>
setAttributes( { authorName: newAuthorName } )
}
/>
<br />
<span>
<RichText
tagName="a"
placeholder={ __( 'Author URL', 'tutorial' ) }
allowedFormats={ [] }
disableLineBreaks
value={ authorURL }
onChange={ ( newAuthorURL ) =>
setAttributes( { authorURL: newAuthorURL } )
}
/>
</span>
</cite>
</blockquote>
);
}
区块现在可以显示和编辑评价正文。修改时不会立刻写入数据库;只有保存文章后,元数据更改才会真正保存。
在 render.php 中更新前端
动态区块用 render.php 生成前端标记。应把这个文件当作前端模板,类似 get_template_part 使用的模板;它主要包含要显示的标记,不适合在其中定义大量 PHP 函数或类。脚手架的文件类似这样:
<p <?php echo get_block_wrapper_attributes(); ?>>
<?php esc_html_e( 'Post Meta Testimonial – hello from a dynamic block!', 'post-meta-testimonial' ); ?>
</p>
先把标记调整成与 JSX 对应的结构:
<blockquote <?php echo get_block_wrapper_attributes(); ?>>
<p>Testimonial will go here</p>
<cite>
<span>Author Name</span>
<br />
<span>
<a href="#">Author URL</a>
</span>
</cite>
</blockquote>
render.php 可以使用三个变量:$attributes 是区块属性数组,$content 是内部内容,$block 是区块实例。本例从属性中取姓名与 URL:
$author_name = $attributes['authorName'];
$author_url = $attributes['authorURL'];
评价正文使用 get_post_meta() 读取。从 $block->context 取得 usesContext 提供的文章 ID,并传入元数据键和 true,表示读取单个值:
$testimonial = get_post_meta( $block->context['postId'], 'testimonial', true );
$author_name = $attributes['authorName'];
下面是原教程的最终标记示例。需要注意,它这里改用了全局 $post->ID,与上一步的区块上下文方式并不一致。若区块嵌入 Query Loop 等位置,应根据实际上下文确认文章 ID,不能把这两种取得 ID 的方式视为始终等价。
<?php
global $post;
$testimonial = get_post_meta( $post->ID, 'testimonial', true );
$author_name = $attributes['authorName'];
$author_url = $attributes['authorURL'];
?>
<blockquote <?php echo get_block_wrapper_attributes(); ?>>
<p><?php echo esc_html( $testimonial ); ?></p>
<cite>
<span><?php echo esc_html( $author_name ); ?></span>
<br />
<span>
<a href="<?php echo esc_url( $author_url ); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html( $author_url ); ?></a>
</span>
</cite>
</blockquote>
最终示例用 esc_html 转义评价和姓名,用 esc_url 转义链接地址,并为新窗口链接设置 noopener noreferrer。esc_html($testimonial) 会把评价中的 HTML 标签作为文字转义显示;尽管保存时使用 wp_kses_post 允许部分 HTML,前端并不会按原来的富文本格式渲染。这一点在采用 RichText 格式时需要明确。
文章元数据区块还有许多使用层次和场景。这个例子给出从注册字段、编辑实体数据,到动态渲染的基本路径;首页查询五个产品的具体查询代码不在原教程中。











暂无评论内容