区块弃用教程
考虑这样一个场景:你创建了一个实现静态区块的插件,但现在你有了一个绝佳的更新想法,这个想法会让你开发的区块变得非常出色。问题是,它已经发布到 WordPress 插件目录,并且该区块正在数百个网站上使用。
现在考虑另一个场景:你的客户刚刚联系你,他们希望你为他们网站开发的定制区块做一些修改。问题是,这是一个静态区块,并且该区块在他们网站各个页面和文章中使用了数十处,甚至数百处。
为什么这些场景对你作为区块开发者来说是个问题?
如果你对静态区块进行了更改,特别是如果你更改了 save() 函数(即决定哪些内容保存到数据库并最终在前端呈现的函数),那么在更改后,下次在编辑器中加载页面时,你会看到“此区块包含意外或无效内容”的错误消息。

发生这种情况是因为区块先前保存的内容结构与新版本将要保存的结构不匹配,因此会出现区块验证错误。这在区块开发过程中非常常见,使用区块的开发者对此非常了解,并且习惯于处理它。
然而,你肯定不希望用户看到此错误消息,因为他们很可能不了解其背后的原因,因此会认为出了问题,从某种意义上说,确实出了问题!
如果你对区块进行了更改,那么内容编辑者和其他用户将在其网站上该区块的每个实例中看到此验证错误,直到他们为网站上出现的每个区块点击“尝试恢复区块”按钮。
因此,在第一种情况下,你可能会惹恼数百名区块用户;在第二种情况下,你可能会让你的客户(或者至少是负责其网站的内容编辑者)感到不安,因为他们需要遍历整个网站并恢复所有已更改区块的实例。
那么你该怎么办?放弃那个绝妙的想法?告诉客户无法进行更改?将其改为动态区块而不是静态区块?创建一个全新的区块并告诉用户使用新的替代版本?
不,当然不是!这些都不需要。
解决方案——区块弃用
你要做的是“弃用”旧版本的区块。弃用为基于早期版本的区块实例提供了一个优雅的回退。这样,内容编辑者将永远不会看到“此区块包含意外或无效内容”的错误消息,但每当他们更新包含你区块的页面或文章时,你的区块就会将更新后的版本保存到数据库,替换旧版本保存的内容。
同样,如果他们正在创建区块的新实例,将保存最新、最及时的内容版本。
在深入探讨如何弃用区块之前,让我们先暂停一下,考虑一下这个问题的范围。
这仅对静态区块是个问题,并且仅对编辑器是个问题。
静态区块有一个 save() 函数,用于将内容保存到文章中。该保存的内容需要进行验证。另一方面,动态区块没有 save() 函数。它们的内容是实时渲染的,因此不会保存到数据库,也就不需要进行验证。因此,你永远不会在动态区块上看到“此区块包含意外或无效内容”的错误消息。要了解更多关于动态区块的信息,它们与静态区块的区别,以及何时应该使用其中之一,请阅读Joni Halabi 的文章。
而且这仅在编辑器中有问题,前端不受区块任何更改的影响。查看网站前端的人将继续看到数据库中 wp_posts 表中存储的任何内容,即使它是使用旧版本的区块保存的,并且尚未使用新版本更新。
那么,让我们看一些区块弃用的例子。
区块弃用——一个简单的例子
使用 @wordpress/create-block 搭建一个新的区块插件:
npx @wordpress/create-block deprecation-example
在 WordPress 管理后台激活新插件,并创建一个使用新区块的文章。
这是你在编辑器中会看到的内容:

这是你在前端会看到的内容:

现在让我们对区块进行更改。记得首先在 wp-content/plugins/deprecation-example 目录中运行 npm start。请注意,本教程中的所有更改都是针对 src 目录中包含的文件。
更改 save.js 中 save() 函数返回的内容。例如,将第 21 行从:
{ 'Deprecation Example – hello from the saved content!' }
改为:
{ 'Deprecation Example – hi from the saved content!' }
保存文件并重新加载编辑器和前端的页面。前端不会改变,毕竟它只是渲染数据库中存储的内容,而数据库中的内容没有改变。
然而,在编辑器中,你会看到你绝对不希望内容编辑者看到的内容,即“此区块包含意外或无效内容。”错误,如果你查看浏览器控制台,你会看到类似这样的内容:

哇——等等! ✋ 不要通过点击“尝试恢复区块”按钮来尝试恢复区块。让我们通过弃用区块来修复它。
在 src 目录中创建一个名为 deprecated.js 的文件。在文件中创建一个 const,命名为 v1,并赋予其一个包含旧版本 save() 函数的对象值:
const v1 = {
save() {
return (
<p { ...useBlockProps.save() }>
{ 'Deprecation Example – hello from the saved content!' }
</p>
);
}
}
因为这使用了 useBlockProps 钩子,你还需要将其导入到 deprecated.js 中:
import { useBlockProps } from '@wordpress/block-editor';
最后,在 deprecated.js 中导出一个仅包含一个项目的数组,即你刚刚创建的对象:
export default [ v1 ];
将数组导入到 index.js 中,并在传递给 registerBlockType 函数调用的配置对象中引用它:
import deprecated from './deprecated';
registerBlockType( metadata.name, {
edit: Edit,
save,
deprecated
} );
保存更改的文件,现在如果你在编辑器中刷新页面,区块验证错误就会消失。如果你更新文章(你可能需要添加一个新区块或对页面进行其他更改),那么新版本的内容将出现在前端。
这就是区块弃用的实际应用——内容编辑者完全不会意识到区块有任何更改。
那么,让我们解释一下刚才发生了什么。当区块编辑器确定保存的内容与 save() 函数生成的版本不匹配时,它会查找传递给 registerBlockType() 函数的区块配置对象中的 deprecated 属性。然后它遍历对象数组(目前只有一个),寻找一个具有与保存内容匹配的 save() 函数的对象。然后使用该对象在编辑器中解析区块,当文章或页面更新并且区块将较新版本的内容保存到数据库时,则使用新版本。
使用区块属性迭代示例
现在让我们尝试一个稍微复杂一点的例子。首先,在 block.json 中添加一个 attributes 对象,并添加一个 text 属性:
"attributes": {
"text": {
"type": "string",
"source": "html",
"selector": "p",
"default": "Deprecation Test"
}
}
这允许文本由用户配置,而不是硬编码。要使其可由用户配置,你需要更新 edit.js。
首先,将 RichText 组件导入到 edit.js 中:
import { useBlockProps, RichText } from '@wordpress/block-editor';
然后更新 Edit() 函数以使用它:
export default function Edit( { attributes, setAttributes } ) {
const onChangeContent = ( val ) => {
setAttributes( { text: val } )
}
return (
<RichText { ...useBlockProps() }
tagName="p"
onChange={ onChangeContent }
value={ attributes.text }
placeholder="Enter text here..."
/>
);
}
记得将 attributes 和 setAttributes 解构到函数的参数列表中。setAttributes 在 onChange 函数中用于更新 text 属性。
现在文本是可编辑的。去试试吧。现在是关键部分——弃用前一个版本并更新 save.js。
在 deprecated.js 中添加一个新的 const,命名为 v2。将其值设置为包含当前版本 save() 函数的对象:
const v2 = {
save() {
return (
<p { ...useBlockProps.save() }>
{ 'Deprecation Example – hi from the saved content!' }
</p>
);
}
}
现在将 RichText 组件导入到 save.js 中:
import { useBlockProps, RichText } from '@wordpress/block-editor';
并更新 save() 函数:
export default function save( { attributes } ) {
return (
<RichText.Content { ...useBlockProps.save() }
tagName="p"
value={ attributes.text }
/>
);
}
最后要做的是将 v2 常量添加到 deprecated.js 导出的数组中:
export default [ v2, v1 ];
请注意,将最新修订版放在数组的开头是一个好习惯。数组从第一个元素开始解析,直到找到匹配的版本,因此这样做意味着首先尝试最新的弃用版本。一般来说,最新的弃用版本最有可能提供匹配,将其放在数组的开头可以避免不必要地处理不太可能匹配的弃用版本。
迁移属性
现在让我们看看在对区块进行更改时经常需要做的另一件事,即迁移属性。该区块目前有一个名为 text 的属性,但假设我们希望新版本使用 content 作为属性名,因为 text 将用于其他目的,而且无论如何,content 对于区块的内容来说是一个更有意义的名称。
首先要做的是确保在 deprecated.js 中复制当前版本的 save() 函数。添加一个新的 const,命名为 v3,并为其分配一个包含最新版本 save() 函数的对象值:
const v3 = {
save( { attributes } ) {
return (
<RichText.Content { ...useBlockProps.save() }
tagName="p"
value={ attributes.text }
/>
);
}
}
由于此版本使用了 RichText 组件,你还需要将 RichText 导入到 deprecated.js 中:
import { useBlockProps, RichText } from '@wordpress/block-editor';
接下来,在 block.json 中重命名属性,将其从 text 更改为 content:
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "p"
}
}
现在我们需要将 Edit() 函数和 save() 函数中对 text 的所有引用更改为引用 content。这是新的 Edit() 函数,有两处需要更改——别忘了 onChange 处理程序中的那一处:
export default function Edit( { attributes, setAttributes } ) {
const onChangeContent = ( val ) => {
setAttributes( { content: val } )
}
return (
<RichText { ...useBlockProps() }
tagName="p"
onChange={ onChangeContent }
value={ attributes.content }
placeholder="Enter text here..."
/>
);
}
这是新的 save() 函数,只有一处引用需要更改:
export default function save( { attributes } ) {
return (
<RichText.Content { ...useBlockProps.save() }
tagName="p"
value={ attributes.content }
/>
);
}
现在我们需要确保弃用版本知道该属性并将其迁移到新名称。在 v3 对象中添加一个 attributes 属性,包含即将弃用的 text 属性:
attributes: {
text: {
type: 'string',
source: 'html',
selector: 'p',
},
},
接下来添加一个 migrate 函数,从传递的对象中解构出 text 属性。该函数应返回新属性,即 content,其值为先前在 text 中的值:
migrate( { text } ) {
return {
content: text,
};
},
我们这里只迁移属性,但 migrate 函数也可用于迁移 innerBlocks。
最终,你的 v3 对象应该如下所示:
const v3 = {
attributes: {
text: {
type: 'string',
source: 'html',
selector: 'p',
},
},
migrate( { text } ) {
return {
content: text,
};
},
save( { attributes } ) {
return (
<RichText.Content { ...useBlockProps.save() }
tagName="p"
value={ attributes.text }
/>
);
}
}
剩下的就是将 v3 对象添加到数组的开头:
export default [ v3, v2, v1 ];
现在,当你刷新包含区块的编辑器页面时,先前在 text 属性中的文本现在位于 content 属性中。你可以通过在编辑器中选择区块并在浏览器的控制台中运行以下命令来确认这一点:
wp.data.select( 'core/block-editor' ).getSelectedBlock().attributes

总结
那么,简单总结一下,传递给 registerBlockType() 函数的区块配置对象可以有一个 deprecated 属性,其值是一个数组。每个弃用版本的区块都是该数组中的一个对象,每个对象可以具有以下属性:
attributessupportssavemigrateisEligible
有关区块弃用的更多信息,包括本文未涵盖的 supports 和 isEligible 属性,请参阅区块编辑器手册中的弃用页面。
查看 Gutenberg 仓库中一些已弃用区块的例子也是个好主意。封面区块中的deprecated.js 文件是一个很好的示例,可以为你自己的弃用提供参考。也看看按钮区块中的那个。这个更难阅读,并且说明了为什么最好将每个版本保存在一个 const 中并将该 const 添加到导出的数组中——它看起来更清晰,更容易阅读,并且随着时间推移和区块的不断演进,更容易添加更多弃用版本。
一个挑战
你以为读完这篇文章就不用做作业了吗?好吧,再想想。😀
目前,RichText 组件在标记中渲染一个 <p> 元素,如果你在前端检查元素,可以看到:

你的挑战是更改区块,使其改为渲染一个 <div> 元素。弃用渲染 <p> 元素的版本。祝你好运!
[如果你在这个挑战中遇到困难,请参阅此 gist 中的解决方案]