Gutenberg区块:如何利用`InnerBlocks`组件实现模板锁定功能?

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 表示不显示添加按钮。stringWPComponent 允许你自定义添加按钮的文本或组件。
__experimentalDirectInsert boolean (实验性属性)如果设置为 true,则允许用户直接从父区块插入新的区块,而无需先选择 InnerBlocks 容器。这可以提高编辑效率。请注意,这是一个实验性属性,可能会在未来的 WordPress 版本中发生变化。

3. 实现模板锁定:templatetemplateLock 的配合

模板锁定是通过结合 templatetemplateLock 属性来实现的。 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. 高级应用:动态模板和条件锁定

templatetemplateLock 属性的值可以是动态的,这意味着我们可以根据不同的条件来改变模板结构和锁定方式。

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 区块开发中非常强大的工具,掌握它可以让你更好地控制区块的结构和用户体验。通过合理地使用 templatetemplateLock 属性,你可以创建各种各样的模板锁定方案,从而满足不同的需求。记住,性能、用户体验和向后兼容性是需要始终考虑的关键因素。

希望今天的分享对大家有所帮助!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注