Gutenberg 区块:利用 InnerBlocks
组件实现模板锁定功能
各位朋友,大家好!今天我们来深入探讨 Gutenberg 区块开发中的一个重要概念:InnerBlocks
组件,以及如何利用它来实现模板锁定功能。模板锁定对于创建一致且用户友好的编辑体验至关重要,尤其是在构建复杂的布局或主题时。
1. InnerBlocks
: 区块的容器
InnerBlocks
组件本质上是一个容器,它允许我们在一个父区块中嵌入其他区块。可以将它视为一个“区块中的区块”的概念。它提供了强大的能力,可以定义允许在父区块中使用的子区块类型,以及它们之间的布局和顺序。
-
基本概念:
InnerBlocks
允许你定义哪些区块可以被插入到父区块中。- 它可以控制这些区块的顺序和数量。
- 它支持预定义的区块结构,即“模板”。
-
作用:
- 创建可复用的布局:例如,一个包含标题和内容的标准文章段落。
- 限制用户编辑:例如,只允许用户修改文本内容,而不允许修改布局。
- 提高编辑体验:通过预定义的模板,简化用户的操作。
2. InnerBlocks
组件的属性
InnerBlocks
组件有一些关键的属性,这些属性决定了它的行为和功能。
属性名 | 类型 | 描述 |
---|---|---|
allowedBlocks |
string[] |
一个字符串数组,指定允许插入到 InnerBlocks 容器中的区块类型。例如,[ 'core/paragraph', 'core/image' ] 表示只允许插入段落和图像区块。如果为空,则允许所有区块。 |
template |
Array<Array<string>> |
一个二维数组,定义了 InnerBlocks 容器的初始结构。每个子数组表示一个区块,包含区块名称和属性。例如,[ [ 'core/paragraph', { placeholder: '输入文本...' } ] ] 表示初始状态包含一个段落区块,并且有一个默认的占位符文本。 |
templateLock |
'all' | 'insert' | false | true |
控制模板的锁定方式。'all' 表示完全锁定模板,用户无法添加、删除或移动区块。'insert' 表示只允许用户插入新的区块,但不能删除或移动现有区块。false (或省略) 表示不锁定模板。true 效果等同于 ‘all’,但在某些情况下可能有所不同,建议使用 ‘all’。 |
renderAppender |
boolean | string | WPComponent |
控制是否显示或自定义添加区块的按钮。true (默认) 表示显示默认的添加按钮。false 表示不显示添加按钮。string 或 WPComponent 允许你自定义添加按钮的文本或组件。 |
__experimentalDirectInsert |
boolean |
(实验性属性)如果设置为 true ,则允许用户直接从父区块插入新的区块,而无需先选择 InnerBlocks 容器。这可以提高编辑效率。请注意,这是一个实验性属性,可能会在未来的 WordPress 版本中发生变化。 |
3. 实现模板锁定:template
和 templateLock
的配合
模板锁定是通过结合 template
和 templateLock
属性来实现的。 template
属性定义了 InnerBlocks
容器的初始结构,而 templateLock
属性则控制用户对该结构的修改权限。
3.1. 示例 1:完全锁定的模板
假设我们想创建一个包含标题和内容区块的“英雄”区块,并且希望用户只能修改标题和内容,而不能删除或移动这些区块。
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
registerBlockType('my-plugin/hero-block', {
title: '英雄区块',
icon: 'shield',
category: 'common',
attributes: {
// 可以添加额外的属性来控制样式等
},
edit: () => {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<InnerBlocks
template={[
['core/heading', { placeholder: '添加标题', level: 2 }],
['core/paragraph', { placeholder: '添加内容' }],
]}
templateLock="all"
/>
</div>
);
},
save: () => {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<InnerBlocks.Content />
</div>
);
},
});
在这个例子中:
template
属性定义了初始结构,包含一个标题区块(core/heading
)和一个段落区块(core/paragraph
)。templateLock="all"
属性完全锁定了模板,用户无法添加、删除或移动这两个区块。用户只能编辑区块的内容。
3.2. 示例 2:允许插入的模板
假设我们想创建一个包含多个列表项的“功能列表”区块,并且希望用户可以添加更多的列表项,但不能删除或移动现有的列表项。
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
registerBlockType('my-plugin/feature-list-block', {
title: '功能列表区块',
icon: 'list-ul',
category: 'common',
edit: () => {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<h3>功能列表</h3>
<InnerBlocks
template={[
['core/list', { values: '<li>功能 1</li><li>功能 2</li>' }],
]}
templateLock="insert"
allowedBlocks={['core/list']} // 限制只能添加列表
/>
</div>
);
},
save: () => {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<InnerBlocks.Content />
</div>
);
},
});
在这个例子中:
template
属性定义了初始结构,包含一个列表区块(core/list
),其中包含两个列表项。templateLock="insert"
属性允许用户插入新的列表项,但不能删除或移动现有的列表项。allowedBlocks={['core/list']}
限制了用户只能添加列表区块。
4. 高级应用:动态模板和条件锁定
template
和 templateLock
属性的值可以是动态的,这意味着我们可以根据不同的条件来改变模板结构和锁定方式。
4.1. 动态模板:根据属性改变结构
假设我们想创建一个“选项卡”区块,并且希望用户可以自定义选项卡的数量。我们可以使用一个属性来存储选项卡的数量,并根据该属性动态生成模板。
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { RangeControl } from '@wordpress/components';
registerBlockType('my-plugin/tabs-block', {
title: '选项卡区块',
icon: 'columns',
category: 'common',
attributes: {
numberOfTabs: {
type: 'number',
default: 2,
},
},
edit: ({ attributes, setAttributes }) => {
const { numberOfTabs } = attributes;
const blockProps = useBlockProps();
// 动态生成模板
const template = Array(numberOfTabs)
.fill()
.map((_, index) => ['my-plugin/tab-item', { title: `选项卡 ${index + 1}` }]);
return (
<>
<InspectorControls>
<RangeControl
label="选项卡数量"
value={numberOfTabs}
onChange={(value) => setAttributes({ numberOfTabs: value })}
min={1}
max={5}
/>
</InspectorControls>
<div { ...blockProps }>
<h3>选项卡</h3>
<InnerBlocks
template={template}
allowedBlocks={['my-plugin/tab-item']}
/>
</div>
</>
);
},
save: () => {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<InnerBlocks.Content />
</div>
);
},
});
// 注册 tab-item 区块 (简化示例)
registerBlockType('my-plugin/tab-item', {
title: '选项卡项',
icon: 'admin-page',
category: 'common',
attributes: {
title: {
type: 'string',
default: '选项卡',
},
},
edit: ({ attributes, setAttributes }) => {
const { title } = attributes;
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<input
type="text"
value={title}
onChange={(e) => setAttributes({ title: e.target.value })}
placeholder="选项卡标题"
/>
<InnerBlocks />
</div>
);
},
save: () => {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<h3>{attributes.title}</h3>
<InnerBlocks.Content />
</div>
);
},
});
在这个例子中:
numberOfTabs
属性存储选项卡的数量。template
属性根据numberOfTabs
的值动态生成。- 用户可以使用
RangeControl
组件来改变选项卡的数量。
4.2. 条件锁定:根据用户角色改变锁定方式
我们可以根据用户的角色来改变模板的锁定方式。例如,我们可以允许管理员修改模板,而限制普通用户只能编辑内容。 这需要与后端的 PHP 代码配合,获取用户角色信息,并传递到前端的 JavaScript 代码中。 以下是一个简化示例,假设我们已经可以通过全局变量 wp.data.select('core').getCurrentUser().roles
获取用户角色信息(实际情况下,这可能需要更复杂的实现)。
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
registerBlockType('my-plugin/conditional-lock-block', {
title: '条件锁定区块',
icon: 'lock',
category: 'common',
edit: () => {
const blockProps = useBlockProps();
// 假设我们可以通过这种方式获取用户角色
const userRoles = wp.data.select('core').getCurrentUser().roles;
const isAdmin = userRoles.includes('administrator');
const templateLock = isAdmin ? false : 'all'; // 管理员不锁定,其他用户完全锁定
return (
<div { ...blockProps }>
<h3>条件锁定区块</h3>
<InnerBlocks
template={[
['core/heading', { placeholder: '添加标题', level: 2 }],
['core/paragraph', { placeholder: '添加内容' }],
]}
templateLock={templateLock}
/>
</div>
);
},
save: () => {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<InnerBlocks.Content />
</div>
);
},
});
5. 注意事项和最佳实践
- 性能: 复杂的模板结构可能会影响编辑器的性能。尽量保持模板的简洁性。
- 用户体验: 模板锁定应该谨慎使用。过度锁定可能会限制用户的创造力。 确保用户清楚地了解哪些部分可以编辑,哪些部分不能编辑。
- 向后兼容性: 在修改模板结构时,要考虑到已存在的文章和页面。 提供迁移方案,以避免数据丢失。
allowedBlocks
的使用: 始终使用allowedBlocks
属性来限制允许插入的区块类型。这可以防止用户插入不兼容的区块,并提高编辑体验。- 模板的嵌套:
InnerBlocks
可以嵌套使用,允许你创建更复杂的布局。但是,要避免过度嵌套,以免影响性能和可维护性。 renderAppender
的自定义: 你可以使用renderAppender
属性来自定义添加区块的按钮。 例如,你可以添加一个带有特定图标和文本的按钮。 或者,你可以完全禁用添加按钮,并提供其他方式来添加区块。__experimentalDirectInsert
的使用:__experimentalDirectInsert
属性可以提高编辑效率,但请注意它是一个实验性属性,可能会在未来的 WordPress 版本中发生变化。 在使用它之前,请仔细阅读文档并进行测试。- 维护性: 将模板定义和逻辑封装在单独的函数或组件中,可以提高代码的可读性和可维护性。 例如,你可以创建一个
getTemplate()
函数,根据不同的条件返回不同的模板。 - 测试: 彻底测试你的区块,以确保模板锁定功能正常工作,并且用户可以按照预期的方式编辑内容。
- 文档: 为你的区块提供清晰的文档,说明如何使用模板锁定功能,以及用户可以编辑哪些部分。
6. 代码示例:更复杂的模板锁定场景
假设我们需要创建一个“人员简介”区块,该区块包含一个图像、一个姓名和一个职称。我们希望用户可以编辑姓名和职称,但只能替换图像,而不能删除图像或移动其他区块。
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps, MediaPlaceholder } from '@wordpress/block-editor';
registerBlockType('my-plugin/person-profile-block', {
title: '人员简介区块',
icon: 'admin-users',
category: 'common',
attributes: {
imageId: {
type: 'number',
},
imageUrl: {
type: 'string',
},
},
edit: ({ attributes, setAttributes }) => {
const { imageId, imageUrl } = attributes;
const blockProps = useBlockProps();
const onSelectImage = (media) => {
setAttributes({
imageId: media.id,
imageUrl: media.url,
});
};
return (
<div { ...blockProps }>
{imageUrl ? (
<img src={imageUrl} alt="人员头像" />
) : (
<MediaPlaceholder
onSelect={onSelectImage}
allowedTypes={['image']}
multiple={false}
labels={{
title: '人员头像',
instructions: '选择或上传一张人员头像。',
}}
/>
)}
<InnerBlocks
template={[
['core/heading', { placeholder: '添加姓名', level: 3 }],
['core/paragraph', { placeholder: '添加职称' }],
]}
templateLock="all"
/>
</div>
);
},
save: ({ attributes }) => {
const { imageUrl } = attributes;
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
{imageUrl && <img src={imageUrl} alt="人员头像" />}
<InnerBlocks.Content />
</div>
);
},
});
在这个例子中:
- 我们使用
MediaPlaceholder
组件来允许用户选择或上传图像。 templateLock="all"
属性锁定了姓名和职称区块,用户只能编辑它们的内容。- 用户可以替换图像,但不能删除它或移动其他区块。
7. InnerBlocks.Content
: 保存区块内容
在 save
函数中,我们使用 <InnerBlocks.Content />
组件来保存 InnerBlocks
容器中的内容。 这个组件会将 InnerBlocks
容器中的所有区块的内容渲染成 HTML。
8. 使用场景表格:不同的锁定策略和应用
场景 | template |
templateLock |
allowedBlocks |
描述 |
---|---|---|---|---|
标准文章段落 | [ [ 'core/heading' ], [ 'core/paragraph' ] ] |
'all' |
[ 'core/heading', 'core/paragraph' ] |
创建一个包含标题和内容的标准文章段落,用户只能编辑内容,不能修改布局。 |
轮播图 | [ [ 'my-plugin/slide' ], [ 'my-plugin/slide' ], [ 'my-plugin/slide' ] ] |
'insert' |
[ 'my-plugin/slide' ] |
创建一个轮播图,用户可以添加更多的幻灯片,但不能删除或移动现有的幻灯片。 |
固定布局的页面 | [ [ 'core/columns', { columns: 2 }, [ [ 'core/column' ], [ 'core/column' ] ] ] ] |
'all' |
[ 'core/columns', 'core/column', 'core/paragraph' ] |
创建一个固定布局的页面,包含两列,用户只能在每列中添加内容,不能修改布局。 |
FAQ 区块 | [ [ 'my-plugin/faq-item' ] ] |
false |
[ 'my-plugin/faq-item' ] |
创建一个 FAQ 区块,用户可以添加、删除和移动 FAQ 项。 |
代码高亮区块 | [ [ 'core/code' ] ] |
'all' |
[ 'core/code' ] |
创建一个代码高亮区块,用户只能编辑代码,不能添加其他区块。 |
团队成员列表 | [ [ 'my-plugin/team-member' ] ] |
'insert' |
[ 'my-plugin/team-member' ] |
创建一个团队成员列表,用户可以添加更多的团队成员,每个团队成员包含图像、姓名和职称。 |
9. 总结要点
InnerBlocks
是 Gutenberg 区块开发中非常强大的工具,掌握它可以让你更好地控制区块的结构和用户体验。通过合理地使用 template
和 templateLock
属性,你可以创建各种各样的模板锁定方案,从而满足不同的需求。记住,性能、用户体验和向后兼容性是需要始终考虑的关键因素。
希望今天的分享对大家有所帮助!