我们已经掌握了一些基础知识,玩了一些 React,现在我们也设置了项目工具。让我们深入构建自定义区块。
文章系列
- 系列简介
- 什么是 Gutenberg?
- create-guten-block 入门
- 现代 JavaScript 语法
- React 101
- 设置自定义 webpack
- 自定义“卡片”区块 (本文)
我们将构建什么
我们将构建一个自定义卡片区块,其中包含图像、标题和摘要。这是网络中非常常见的模式,它也让我们可以查看一些核心 Gutenberg 组件,以及核心 WordPress 元素,例如媒体库。我们还将使用 JSX 针对前端标记玩一些显示逻辑。

我们辉煌的自定义卡片区块!
在本教程中,我们将专注于此区块的 CMS 方面。不过,它在前端呈现了一些漂亮、简洁的标记。如果你愿意,可以扩展此区块来包含前端样式。
入门
我们要做的第一件事是打开我们在 上一节 中创建的 block.js
文件。在您的活动插件文件夹中,它位于 blocks/src/block/block.js
。
block.js
中的所有内容,并与教程一起从头开始编写您的代码。在该文件的顶部,添加以下内容
const { RichText, MediaUpload, PlainText } = wp.editor;
const { registerBlockType } = wp.blocks;
const { Button } = wp.components;
我们在 第三部分 中介绍了解构赋值。这是一个很好的例子,您将在 Gutenberg 代码中经常看到。在这里,wp.components
包含 Button
以外的其他内容,但我们只想要 Button
,因此我们只会获取它。这很方便,因为它可以防止我们不得不编写类似 wp.components.Button
的内容,这对于保持代码简洁轻巧非常有用。
我们已经解决了这个问题,现在让我们导入 Sass 文件。这样 webpack 就能检测到它们。
import './style.scss';
import './editor.scss';
现在让我们开始编写为区块提供动力的组件。在这些行下方,添加以下内容
registerBlockType('card-block/main', {
title: 'Card',
icon: 'heart',
category: 'common'
});
这段代码告诉 Gutenberg,“嘿,我有一个区块要添加到您的集合中。它被称为“卡片”,它有一个“心形”图标,它应该位于“通用”类别中。” 这是我们组件的基本定义,让我们添加更多代码。
这应该看起来很熟悉——还记得我们在 第二部分,create-guten-block 中的挑战吗?如果您需要提醒,请查看此处。前六个相对简单,涉及替换字符串或 HTML 片段。第七个项目,“使段落文本可编辑”,实现起来要复杂得多,旨在让您思考一下。现在是时候了,我们将真正使 Gutenberg 中的一些文本可编辑!
您可能还记得我们在 上一篇文章 中使用的 PHP register_block_type
函数中的 registerBlockType
函数。虽然该函数从 WordPress 的服务器端注册了区块,但此函数将我们的区块注册到客户端的 React 生态系统中。这两个函数都需要才能创建一个使用 React 的区块,并且它们的注册名称(card-block/main
)必须匹配。
添加以下代码,但请确保在 'common'
后添加逗号,使其看起来像这样:'common',
。
以下是代码
attributes: {
title: {
source: 'text',
selector: '.card__title'
},
body: {
type: 'array',
source: 'children',
selector: '.card__body'
},
imageAlt: {
attribute: 'alt',
selector: '.card__image'
},
imageUrl: {
attribute: 'src',
selector: '.card__image'
}
}
在这里,我们定义了区块的可编辑属性和分配给它们的 DOM 选择器。此 attribute
对象的工作方式与 React state
对象非常相似。它甚至还有一个非常相似的更新方法,名为 setAttributes
。不过,我们稍后会讲到这一点。
关于属性和状态
它可能看起来像一个简单的 JavaScript 对象,但 attributes
那部分引入了一系列 WordPress 主题开发人员脑海中全新的概念,其中最重要的是状态。状态的概念在计算机科学,以及现实生活中,都有着悠久的历史。几乎所有事物都有状态。您现在的咖啡杯处于什么状态?空着,还是几乎空着?您的衣服呢?您的鞋子是脏的还是新的?您的身体呢?您是疲惫不堪,还是精力充沛?
从高层次上讲,状态简单地指的是某件事的当前情况。在计算机科学中,那件事是一个计算机程序,而该程序可能比我们在网络上创建的程序要简单得多。例如,一台自动售货机。自动售货机有一个状态,每次您投入硬币时都会更新。当机器的状态达到预定义的值时,例如 1.25 美元,机器就会知道允许您选择零食。
在 Gutenberg 中,属性跟踪区块中数据的当前情况。属性是我们能够与 Gutenberg 中的自定义字段建立的最接近的类比,但它们仅存在于 Gutenberg 和 JavaScript 的上下文中。例如,让我们看看上面 title
的属性
title: {
source: 'text',
selector: 'card__title'
},
当 Gutenberg 启动时,它会说,“我需要在一个名为 .card__title
的选择器中找到一些文本,并将 title
的值填充为我找到的内容。”
Gutenberg 中的属性不像自定义字段那样直接连接到数据库,自定义字段连接到 post_meta
。条目 source
和 selector
是 Gutenberg 用于填充每个区块状态的指令。当我们加载编辑器时,它会遵循这些指令,并根据数据库中保存的标记(在指示此类型区块的 HTML 注释之间)为 title
分配一个值。我们没有看到 attributes
中注册的 title
的值,但是如果我要访问 props.attributes.title
,我会得到 .card__title
中存在的任何 text
。
添加我们的编辑函数
让我们添加一些代码。在 attributes
对象的结束括号 }
后添加以下内容。像以前一样,确保您添加一个尾随逗号,使其看起来像这样 },
。
在之后添加以下代码
edit({ attributes, className, setAttributes }) {
return (
);
}
因此,我们使用另一个解构赋值来选择性地选择传递给编辑函数的参数。最重要的两个参数是 attributes
和 setAttributes
。attributes
参数与 attributes
区块相同,但它是当前的、响应式状态。这意味着,如果 setAttributes
函数更新了 attributes
中的某个值,它将自动更新任何引用它的位置,这与我们在 第三部分 中的 React 组件类似。
此函数中有一个很大的 return
。你能猜出它里面有什么吗?没错!我们将把一些 JSX 放进去。在 return
圆括号中添加以下内容
<div className="container">
<MediaUpload
onSelect={ media => { setAttributes({ imageAlt: media.alt, imageUrl: media.url }); } }
type="image"
value={ attributes.imageID }
render={ ({ open }) => getImageButton(open) }
/>
<PlainText
onChange={ content => setAttributes({ title: content }) }
value={ attributes.title }
placeholder="Your card title"
className="heading"
/>
<RichText
onChange={ content => setAttributes({ body: content }) }
value={ attributes.body }
multiline="p"
placeholder="Your card text"
/>
</div>
好的,这里有很多东西,但它们都是我们在本系列之前部分中介绍过的内容。我们在这里看到的是一个包含三个现有的 Gutenberg 组件 的容器。对于每个组件,我们都将其值设置为相关的 attribute
,设置相关的占位符,以及 onChange/onSelect
处理程序。我们还将一个自定义渲染器传递给 <MediaUpload />
,我们稍后会介绍它。
每个onChange
处理程序都是一个方便的小表达式,它将触发onChange
的新内容传递给setAttributes
函数,我们在此设置要更新哪个attributes
对象。 此更新随后级联到该属性的任何引用,其中内容将像魔法一样更新。 <MediaUpload />
元素具有一个onSelect
事件,该事件在用户选择或上传项目到媒体库时触发。
说到<MediaUpload />
元素,您会注意到有一个自定义的render
属性,它引用了getImageButton
函数。 让我们接下来编写它。 在edit
函数中的return
上方添加以下内容
const getImageButton = (openEvent) => {
if(attributes.imageUrl) {
return (
<img
src={ attributes.imageUrl }
onClick={ openEvent }
className="image"
/>
);
}
else {
return (
<div className="button-container">
<Button
onClick={ openEvent }
className="button button-large"
>
Pick an image
</Button>
</div>
);
}
};
此函数的作用是检测attributes
对象中是否存在imageUrl
。 如果存在,它将呈现该<img />
标签,并允许用户单击它以选择另一个。 如果没有图像,它将呈现一个WordPress<Button />
,提示用户选择一个图像。 这会调用传递到函数中的相同openEvent
。
为了在本教程中保持简单,我们将单击绑定到<img />
元素。 您应该考虑构建一些利用<button />
的精美内容,以使您的生产就绪块具有更好的可访问性支持。
好的,我们的edit
函数完成了。 考虑到它的实际作用,那里没有太多代码,这很棒!
添加我们的保存函数
现在,我们已经编写了块的Gutenberg编辑器端,这是最难的部分。 现在我们要做的就是告诉Gutenberg我们希望块对内容执行的操作。 使用来自attributes
的相同反应数据,我们也可以实时渲染出我们的前端标记。 这意味着当有人切换到块上的HTML编辑模式时,它将是最新的。 如果你在HTML编辑模式下编辑它,视觉模式也将保持最新。 非常有用。
让我们深入了解一下。 在我们的edit
函数之后,添加一个逗号,使其看起来像},
,然后在新行上添加以下内容
save({ attributes }) {
const cardImage = (src, alt) => {
if(!src) return null;
if(alt) {
return (
<img
className="card__image"
src={ src }
alt={ alt }
/>
);
}
// No alt set, so let's hide it from screen readers
return (
<img
className="card__image"
src={ src }
alt=""
aria-hidden="true"
/>
);
};
return (
<div className="card">
{ cardImage(attributes.imageUrl, attributes.imageAlt) }
<div className="card__content">
<h3 className="card__title">{ attributes.title }</h3>
<div className="card__body">
{ attributes.body }
</div>
</div>
</div>
);
}
看起来很像edit
函数,对吧? 让我们逐步执行它。
我们首先使用解构赋值从传递的参数中提取attributes
,就像前面的edit
函数一样。
然后我们有另一个图像辅助函数,它首先检测是否存在图像,如果不存在则返回null
。 请记住:如果我们希望它不渲染任何内容,则在JSX中返回null
。 此辅助程序接下来要做的是,如果存在替代文本,则呈现一个略有不同的<img />
标签。 对于后者,它通过添加aria-hidden="true"
和设置一个空白的alt
属性将其从屏幕阅读器中隐藏。
最后,我们的return
吐出一个不错的.card
块,它具有干净的,由BEM驱动的标记,该标记将在我们主题的前端加载。
这就是我们的save
函数。 我们离完成块只有一步之遥!
添加一些样式
好的,我们必须做这些,我们就完成了。 你们中那些善于观察的人可能已经注意到,有些引用指向className
。 这些引用的是我们的editor.scss
规则,所以让我们添加它们。
打开editor.scss
,它位于与block.js
相同的目录中。 添加以下内容
@import '../common';
.gutenberg { // This may need to be .wp-block in WordPress 5+
.container {
border: 1px solid $gray;
padding: 1rem;
}
.button-container {
text-align: center;
padding: 22% 0;
background: $off-white;
border: 1px solid $gray;
border-radius: 2px;
margin: 0 0 1.2rem 0;
}
.heading {
font-size: 1.5rem;
font-weight: 600;
}
.image {
height: 15.7rem;
width: 100%;
object-fit: cover;
}
}
这是一些松散的CSS,可以为我们的块提供一些卡片样式。 请注意,它全部嵌套在.gutenberg
类中? 这样做是为了对抗某些核心样式的特异性。 在编辑器中,<div class="gutenberg"
包装在帖子编辑器屏幕的块区域周围,因此我们可以确保仅通过此嵌套来影响这些元素。 您可能还会注意到我们正在导入另一个Sass文件,所以让我们填充它。
打开common.scss
,它位于src
目录中,它是我们当前所在的block
目录的父目录。
/*
* Common SCSS can contain your common variables, helpers and mixins
* that are shared between all of your blocks.
*/
// Colors
$gray: #cccccc;
$off-white: #f1f1f1;
无论如何,猜猜看? 我们已经创建了一个自定义的卡片块!! 让我们试用一下。
首先,检查您的块是否都很好。 完整的block.js
文件应如下所示
const { RichText, MediaUpload, PlainText } = wp.editor;
const { registerBlockType } = wp.blocks;
const { Button } = wp.components;
// Import our CSS files
import './style.scss';
import './editor.scss';
registerBlockType('card-block/main', {
title: 'Card',
icon: 'heart',
category: 'common',
attributes: {
title: {
source: 'text',
selector: '.card__title'
},
body: {
type: 'array',
source: 'children',
selector: '.card__body'
},
imageAlt: {
attribute: 'alt',
selector: '.card__image'
},
imageUrl: {
attribute: 'src',
selector: '.card__image'
}
},
edit({ attributes, className, setAttributes }) {
const getImageButton = (openEvent) => {
if(attributes.imageUrl) {
return (
<img
src={ attributes.imageUrl }
onClick={ openEvent }
className="image"
/>
);
}
else {
return (
<div className="button-container">
<Button
onClick={ openEvent }
className="button button-large"
>
Pick an image
</Button>
</div>
);
}
};
return (
<div className="container">
<MediaUpload
onSelect={ media => { setAttributes({ imageAlt: media.alt, imageUrl: media.url }); } }
type="image"
value={ attributes.imageID }
render={ ({ open }) => getImageButton(open) }
/>
<PlainText
onChange={ content => setAttributes({ title: content }) }
value={ attributes.title }
placeholder="Your card title"
className="heading"
/>
<RichText
onChange={ content => setAttributes({ body: content }) }
value={ attributes.body }
multiline="p"
placeholder="Your card text"
formattingControls={ ['bold', 'italic', 'underline'] }
isSelected={ attributes.isSelected }
/>
</div>
);
},
save({ attributes }) {
const cardImage = (src, alt) => {
if(!src) return null;
if(alt) {
return (
<img
className="card__image"
src={ src }
alt={ alt }
/>
);
}
// No alt set, so let's hide it from screen readers
return (
<img
className="card__image"
src={ src }
alt=""
aria-hidden="true"
/>
);
};
return (
<div className="card">
{ cardImage(attributes.imageUrl, attributes.imageAlt) }
<div className="card__content">
<h3 className="card__title">{ attributes.title }</h3>
<div className="card__body">
{ attributes.body }
</div>
</div>
</div>
);
}
});
如果您满意,让我们启动webpack。 在终端中的当前插件目录中,运行以下命令
npx webpack --watch
读者Moritz Karliczek写信说他们在这里遇到了错误:TypeError: Cannot read property 'bindings' of null
。 我们现在最好的猜测是,本教程是针对Babel 6编写的,但是Babel 7现在已经发布了。 由您决定是否要确保您的Babel内容锁定在6上,或者是否要自己解决7的问题。 如果您已经克服了这个问题,并且有好的信息供我们更新本系列,请写信!
Karen White联系了我们,遇到了同样的问题,发现将babelrc
文件更新为使用"presets": ["@babel/preset-env"]
可以使其正常工作。
这与本系列的先前部分略有不同,因为我们添加了--watch
参数。 这实际上会监视您的js
文件,并在它们更改时重新运行webpack
。
启动编辑器!
让我们通过在WordPress后端加载帖子来加载Gutenberg编辑器。 在Gutenberg编辑器中,单击小加号图标,然后查看“块”选项卡,它就在那里:我们很棒的新卡片块!

继续试用它,并在其中添加一些内容。 感觉很好吧?
以下是一段快速视频,展示了您现在应该看到的内容,以及您新奇的卡片块
就这样,您完成了🎉
块与自定义字段
虽然Gutenberg确实为我们提供了从用户体验中自定义数据输入结构的能力,但在后端,它与当前的WYSIWYG编辑器没有什么不同。 从块中保存的数据是wp_posts
数据库表中post_content
列的一部分-它不是像自定义字段那样在wp_postmeta
中单独存储。 这意味着目前,我们无法从另一个帖子中访问卡片块中的数据,就像使用标准的Advanced Custom Fields设置创建title
,image
和content
的自定义字段那样。
也就是说,我可以看到一些真正有趣的插件出现,这些插件提供了一种将数据从块移植到网站其他部分的方法。 使用WordPress REST API,可能性几乎是无限的! 在我们的屏幕录制中,Andy和我尝试将API请求合并到我们的卡片块中,尽管结果并不完全如预期,但工具已经到位,您可以体验到将来使用Gutenberg可能实现的功能。 时间会证明一切!
总结和下一步
我们一起经历了一段旅程! 让我们列出您在本系列中学到的知识
- 您已经了解了一些关于核心现代JavaScript的知识,并掌握了ES6
- 您已经了解了JSX的基础知识
- 您利用了这些新知识,并从头开始构建了一个React组件
- 然后您了解了webpack的工作原理,并为Gutenberg块开发编写了一个可扩展的配置文件
- 最后,您从头开始构建了自定义的Gutenberg卡片块
那么,从这里您可以去哪里呢? 现在您已经从本系列中获得了扎实的知识基础,可以进行一些进一步的学习。 已经有很多很棒的资源可以帮助您学习
- 编写您的第一个块类型,在WordPress.org上(Andy:这就是我学习块工作原理的方式)
- Gutenberg课程,由Zac Gordon和Joe Casabona提供
- 创建自定义Gutenberg块,由Pete Tasker提供
- 自定义块的解剖,由Morgan Kay提供
- 今天扩展Gutenberg的一千零一种方法,由Riad Benguella提供
- WordPress.tv上的Gutenberg演讲
一些有趣的案例研究
关注这些资源,以了解有关该项目的最新信息
Gutenberg的实验性内容
- Gutenberg 自定义字段 插件
- Atomic Blocks 插件和主题
一旦 Gutenberg 成为 WordPress 核心版本 5.0 的一部分(发布日期待定),您也可以在 WordPress 插件目录 中发布一个有用的自定义区块。肯定有空间可以容纳一些方便的组件,例如您刚刚构建的卡片区块。
我们希望您喜欢这个系列,因为我们制作它的时候确实很享受。我们真的希望这能帮助您进入 Gutenberg 并构建一些很酷的东西。您也应该将您构建的东西的链接发送给我们!
文章系列
- 系列简介
- 什么是 Gutenberg?
- create-guten-block 入门
- 现代 JavaScript 语法
- React 101
- 设置自定义 webpack
- 自定义“卡片”区块 (本文)
您好,
我看到一个错误
ReferenceError: styles 未定义
这似乎与 block.js 中的第 45 行有关。当我删除
style={ styles.buttonContainer }
时,一切正常。可能我在教程中遗漏了一步
我也遇到了这个问题,教程开头处的代码示例与教程结束时提供的完整文件不同。
Scott,很棒的发现。谢谢。
我已经更新了它 :)
感谢您撰写这个系列。我目前没有任何 WordPress 项目,但我一定会在我进行项目时回来查看。
谢谢,Thomas!我希望这对您在进行 WordPress 项目时有所帮助 :)
很喜欢这个系列!我希望你能继续并涵盖诸如向区块添加检查器控件或使用 PHP 服务器端渲染构建区块等主题。
我是不是错过了您为
src/blocks.js
输入代码的地方?那是 Webpack 配置的入口点 - 我通过import './block/block.js';
使其正常工作,这有点像是我自己猜的。除此之外,教程很棒,谢谢。Steve,你抓住了问题。谢谢。我已经在 上一篇文章 中添加了更新。
如果我想在区块中添加类似“页面链接”这样的字段,并且想查找如何完成它的正确语法/示例,最好的参考资料是什么?
例如,我注意到有 MediaUpload、PlainText 和 RichText 组件,我想知道在哪里可以看到完整的组件选项列表。
所以,文档目前还不太有用,但我相信它会变得更好。Lara 和我都发现深入研究源代码很有用。以下是你想要查找内容的良好起点:https://github.com/WordPress/gutenberg/tree/master/components.
其中一些组件也有特定的文档。
我和 Josiah 在这里,你能告诉我们在哪里可以查找我们在区块中可以使用组件的完整列表,比如 MediaUpload、RichText 等吗?
好的,我认为我明白了,组件列表可以在 editor/components 目录中找到
https://github.com/WordPress/gutenberg/tree/cd848e9161414c1e5418a087edc483e314dcae55/editor/components
很棒的系列!一条评论和几个问题
使用“create-guten-block”生成的默认 eslint 设置会将您的代码中的许多内容标记为错误(尽管它们只是样式上的)。我花了相当长的时间将 eslint 的一些设置设置为“off”。
我可以创建一个自定义组来保存我的区块集吗?是否只需将“common”替换为一个名称就足够了?
我可以创建嵌套区块吗?比如创建一个渲染
section
标签的区块,然后允许在其中添加其他区块,比如这个卡片区块?是的,ESLint 规则确实有可能发生冲突。尤其是样式规则。也许我写的一篇关于基础知识的 文章 可以帮助您开始使用自己的配置?
在嵌套区块方面,这篇文章 似乎包含一些不错的信息。
感谢您提供这个有用的教程系列。我会将其中很多内容应用到我的未来项目中。
我发现,使用 Gutenberg v2.9.2,我得到一个控制台警告,说
wp.block.RichText
以及MediaUpload
和PlainText
组件将在 3.1 版本中被弃用,并建议使用wp.editor.RichText
等。只需更改block.js
文件的第一行即可解决此问题。感谢您提醒。Gutenberg 不断变化,所以我现在将更新这篇文章 :)
我需要在名为
.card__title
的选择器中查找一些文本,并将标题的值填充为我找到的任何内容。select card__title 位于保存函数中。我们应该使用编辑类标题吗?因为那是我们试图获取的纯文本内容。
您好,
在您的帮助下,我已经成功地制作了第一个区块,用于在 iframe 中嵌入 google slide,非常感谢!!
我只是使用 PlainText 来获取 url,并将其替换为 iframe。
如果设置了 url,我只是无法预览 iframe... 是否可以提供一个用于纯文本渲染的示例?
谢谢
您最好前往 Gutenberg GitHub 仓库,那里有很多东西可以查看 :)
有人成功地将这些项目之一检入 Github,然后在另一台计算机上克隆该仓库以在上面工作吗?每次我尝试时,区块都会呈现“无法渲染”的错误图形,而不是区块本身。即使我将其从帖子中删除并重新添加。我只能在生成区块的计算机上工作。两台计算机都在 MAMP Pro 中运行网站,使用相同的 WP 和 Gutenberg 版本。
在任一台计算机上创建的新区块都可以正常工作 - 但我无法将项目在它们之间移动。