Gutenberg区块:如何利用`InnerBlocks`组件构建灵活的内容布局?

Gutenberg 区块:利用 InnerBlocks 组件构建灵活的内容布局

大家好!今天我们来深入探讨 Gutenberg 区块开发中一个非常强大的组件:InnerBlocks。它允许我们在自定义区块内部嵌套其他区块,从而构建出极其灵活和可复用的内容布局。我们将从基础概念入手,逐步深入到高级用法,并通过丰富的代码示例来帮助大家理解。

1. InnerBlocks 基础:嵌套区块的容器

InnerBlocks 本质上是一个区块容器。它允许你在父区块的编辑界面中插入、移动、删除其他区块,这些区块被称为子区块。父区块负责管理和控制子区块的整体布局,而子区块则负责各自的内容和样式。

1.1 基本用法:允许所有区块

最简单的用法是允许 InnerBlocks 包含所有已注册的区块。这可以通过在父区块的 editsave 函数中使用 InnerBlocks 组件来实现。

// 编辑函数 (edit)
edit: (props) => {
  return (
    <div className={props.className}>
      <InnerBlocks />
    </div>
  );
},

// 保存函数 (save)
save: (props) => {
  return (
    <div className={props.className}>
      <InnerBlocks.Content />
    </div>
  );
},

在这个例子中,edit 函数使用 InnerBlocks 组件渲染一个区块编辑器,允许用户在父区块内部添加任何类型的区块。save 函数使用 InnerBlocks.Content 组件来输出子区块的内容。

1.2 指定允许的区块类型:allowedBlocks 属性

为了更好地控制布局,我们可以使用 allowedBlocks 属性来指定 InnerBlocks 允许包含的区块类型。allowedBlocks 属性接受一个区块名称数组。

// 编辑函数 (edit)
edit: (props) => {
  const ALLOWED_BLOCKS = [ 'core/paragraph', 'core/image', 'core/heading' ];

  return (
    <div className={props.className}>
      <InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } />
    </div>
  );
},

// 保存函数 (save)
save: (props) => {
  return (
    <div className={props.className}>
      <InnerBlocks.Content />
    </div>
  );
},

在这个例子中,InnerBlocks 只允许包含段落、图片和标题区块。

1.3 初始内容:template 属性

template 属性允许我们为 InnerBlocks 提供初始内容。它接受一个区块数组,每个区块都包含 nameattributes 属性。

// 编辑函数 (edit)
edit: (props) => {
  const TEMPLATE = [
    [ 'core/paragraph', { placeholder: '在此输入介绍文字...' } ],
    [ 'core/image' ],
    [ 'core/heading', { level: 3, placeholder: '标题' } ],
  ];

  return (
    <div className={props.className}>
      <InnerBlocks template={ TEMPLATE } />
    </div>
  );
},

// 保存函数 (save)
save: (props) => {
  return (
    <div className={props.className}>
      <InnerBlocks.Content />
    </div>
  );
},

在这个例子中,InnerBlocks 初始包含一个带有占位符的段落、一个图片区块和一个 3 级标题区块。

1.4 锁定区块:templateLock 属性

templateLock 属性允许我们锁定 InnerBlocks 的模板,防止用户添加、删除或移动区块。它可以设置为 allinsert

  • all: 锁定所有区块,用户无法添加、删除或移动区块。
  • insert: 只锁定区块的位置,用户可以编辑区块的内容,但无法添加、删除或移动区块。
// 编辑函数 (edit)
edit: (props) => {
  const TEMPLATE = [
    [ 'core/paragraph', { placeholder: '在此输入介绍文字...' } ],
    [ 'core/image' ],
    [ 'core/heading', { level: 3, placeholder: '标题' } ],
  ];

  return (
    <div className={props.className}>
      <InnerBlocks template={ TEMPLATE } templateLock="all" />
    </div>
  );
},

// 保存函数 (save)
save: (props) => {
  return (
    <div className={props.className}>
      <InnerBlocks.Content />
    </div>
  );
},

在这个例子中,InnerBlocks 的模板被完全锁定,用户无法修改初始内容。

2. 更高级的控制:useInnerBlocksProps Hook

useInnerBlocksProps Hook 提供了更精细的控制,允许我们自定义 InnerBlocks 的行为和属性。

import { useInnerBlocksProps } from '@wordpress/block-editor';

// 编辑函数 (edit)
edit: (props) => {
  const ALLOWED_BLOCKS = [ 'core/paragraph', 'core/image' ];
  const innerBlocksProps = useInnerBlocksProps(
    { className: 'my-custom-inner-blocks' },
    { allowedBlocks: ALLOWED_BLOCKS }
  );

  return (
    <div className={props.className}>
      <div { ...innerBlocksProps } />
    </div>
  );
},

// 保存函数 (save)
save: (props) => {
  const blockProps = useBlockProps.save( props );
  return (
    <div { ...blockProps }>
      <InnerBlocks.Content />
    </div>
  );
},

在这个例子中,useInnerBlocksProps Hook 用于创建一个包含自定义类名和允许的区块类型的 InnerBlocks 属性对象。然后,这些属性被传递给一个 div 元素,该元素作为 InnerBlocks 的容器。

3. 自定义区块与 InnerBlocks 的结合:构建复杂布局

InnerBlocks 的真正威力在于它可以与自定义区块结合,构建出各种复杂的布局。例如,我们可以创建一个“列”区块,允许用户在其中添加其他区块,从而实现多列布局。

3.1 创建“列”区块

首先,我们需要创建一个“列”区块,它将使用 InnerBlocks 来管理其内容。

// 列区块定义
registerBlockType( 'my-plugin/column', {
  title: '列',
  icon: 'columns',
  category: 'common',
  attributes: {
    width: {
      type: 'string',
      default: '50%',
    },
  },
  edit: (props) => {
    const { attributes, setAttributes } = props;
    const { width } = attributes;

    const onChangeWidth = (newWidth) => {
      setAttributes({ width: newWidth });
    };

    return (
      <div className={props.className} style={{ width: width }}>
        <div className="column-controls">
          <label>列宽:</label>
          <input
            type="text"
            value={width}
            onChange={(e) => onChangeWidth(e.target.value)}
          />
        </div>
        <InnerBlocks />
      </div>
    );
  },
  save: (props) => {
    const { attributes } = props;
    const { width } = attributes;

    return (
      <div className={props.className} style={{ width: width }}>
        <InnerBlocks.Content />
      </div>
    );
  },
} );

在这个例子中,“列”区块有一个 width 属性,用于控制列的宽度。edit 函数包含一个输入框,允许用户修改列的宽度。InnerBlocks 组件用于管理列的内容。

3.2 创建“行”区块

接下来,我们需要创建一个“行”区块,它将包含多个“列”区块。

// 行区块定义
registerBlockType( 'my-plugin/row', {
  title: '行',
  icon: 'layout',
  category: 'common',
  edit: (props) => {
    return (
      <div className={props.className}>
        <InnerBlocks
          allowedBlocks={['my-plugin/column']}
          template={[['my-plugin/column'], ['my-plugin/column']]}
        />
      </div>
    );
  },
  save: (props) => {
    return (
      <div className={props.className}>
        <InnerBlocks.Content />
      </div>
    );
  },
} );

在这个例子中,“行”区块只允许包含“列”区块。template 属性用于初始化两个“列”区块。

3.3 样式调整

为了使布局看起来更美观,我们需要添加一些 CSS 样式。

.wp-block-my-plugin-row {
  display: flex;
  flex-direction: row;
}

.wp-block-my-plugin-column {
  flex: 1;
  padding: 10px;
  border: 1px solid #ccc;
}

.column-controls {
  margin-bottom: 10px;
}

.column-controls label {
  margin-right: 5px;
}

3.4 完整代码示例

下面是完整的代码示例,包括“列”区块和“行”区块的定义以及 CSS 样式。

// 列区块定义
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks } from '@wordpress/block-editor';

registerBlockType( 'my-plugin/column', {
  title: '列',
  icon: 'columns',
  category: 'common',
  attributes: {
    width: {
      type: 'string',
      default: '50%',
    },
  },
  edit: (props) => {
    const { attributes, setAttributes } = props;
    const { width } = attributes;

    const onChangeWidth = (newWidth) => {
      setAttributes({ width: newWidth });
    };

    return (
      <div className={props.className} style={{ width: width }}>
        <div className="column-controls">
          <label>列宽:</label>
          <input
            type="text"
            value={width}
            onChange={(e) => onChangeWidth(e.target.value)}
          />
        </div>
        <InnerBlocks />
      </div>
    );
  },
  save: (props) => {
    const { attributes } = props;
    const { width } = attributes;

    return (
      <div className={props.className} style={{ width: width }}>
        <InnerBlocks.Content />
      </div>
    );
  },
} );

// 行区块定义
registerBlockType( 'my-plugin/row', {
  title: '行',
  icon: 'layout',
  category: 'common',
  edit: (props) => {
    return (
      <div className={props.className}>
        <InnerBlocks
          allowedBlocks={['my-plugin/column']}
          template={[['my-plugin/column'], ['my-plugin/column']]}
        />
      </div>
    );
  },
  save: (props) => {
    return (
      <div className={props.className}>
        <InnerBlocks.Content />
      </div>
    );
  },
} );

// CSS 样式 (style.scss 或 style.css)
.wp-block-my-plugin-row {
  display: flex;
  flex-direction: row;
}

.wp-block-my-plugin-column {
  flex: 1;
  padding: 10px;
  border: 1px solid #ccc;
}

.column-controls {
  margin-bottom: 10px;
}

.column-controls label {
  margin-right: 5px;
}

4. InnerBlocks 的高级用法

除了上述基本用法之外,InnerBlocks 还有一些高级用法,可以进一步提高其灵活性和可定制性。

4.1 动态模板:根据属性值生成模板

我们可以根据父区块的属性值动态生成 InnerBlocks 的模板。例如,我们可以创建一个“选项卡”区块,根据用户选择的选项卡数量动态生成选项卡内容区块。

// 选项卡区块定义
registerBlockType( 'my-plugin/tabs', {
  title: '选项卡',
  icon: 'tab',
  category: 'common',
  attributes: {
    tabCount: {
      type: 'number',
      default: 2,
    },
  },
  edit: (props) => {
    const { attributes, setAttributes } = props;
    const { tabCount } = attributes;

    const onChangeTabCount = (newTabCount) => {
      setAttributes({ tabCount: newTabCount });
    };

    const generateTemplate = () => {
      const template = [];
      for (let i = 0; i < tabCount; i++) {
        template.push(['my-plugin/tab-content', { title: `选项卡 ${i + 1}` }]);
      }
      return template;
    };

    return (
      <div className={props.className}>
        <label>选项卡数量:</label>
        <input
          type="number"
          value={tabCount}
          onChange={(e) => onChangeTabCount(parseInt(e.target.value))}
        />
        <InnerBlocks
          allowedBlocks={['my-plugin/tab-content']}
          template={generateTemplate()}
          templateLock="insert"
        />
      </div>
    );
  },
  save: (props) => {
    return (
      <div className={props.className}>
        <InnerBlocks.Content />
      </div>
    );
  },
} );

// 选项卡内容区块定义
registerBlockType( 'my-plugin/tab-content', {
  title: '选项卡内容',
  icon: 'text',
  category: 'common',
  attributes: {
    title: {
      type: 'string',
      default: '选项卡',
    },
  },
  edit: (props) => {
    const { attributes, setAttributes } = props;
    const { title } = attributes;

    const onChangeTitle = (newTitle) => {
      setAttributes({ title: newTitle });
    };

    return (
      <div className={props.className}>
        <h3>{title}</h3>
        <label>选项卡标题:</label>
        <input
          type="text"
          value={title}
          onChange={(e) => onChangeTitle(e.target.value)}
        />
        <InnerBlocks />
      </div>
    );
  },
  save: (props) => {
    const { attributes } = props;
    const { title } = attributes;

    return (
      <div className={props.className}>
        <h3>{title}</h3>
        <InnerBlocks.Content />
      </div>
    );
  },
} );

在这个例子中,generateTemplate 函数根据 tabCount 属性值动态生成 InnerBlocks 的模板。

4.2 自定义占位符:提示用户添加内容

我们可以使用 Placeholder 组件来为 InnerBlocks 添加自定义占位符,提示用户添加内容。

import { Placeholder, Button } from '@wordpress/components';

// 编辑函数 (edit)
edit: (props) => {
  const { clientId } = props;
  const hasInnerBlocks = useSelect(
    (select) => select( 'core/block-editor' ).hasInnerBlocks( clientId ),
    [ clientId ]
  );

  return (
    <div className={props.className}>
      { ! hasInnerBlocks ? (
        <Placeholder
          icon="welcome-add-page"
          label="添加内容"
          instructions="在此添加内容区块。"
        >
          <Button
            isPrimary
            onClick={ () => {
              const block = createBlock( 'core/paragraph' );
              insertBlock( block, undefined, clientId );
            } }
          >
            添加段落
          </Button>
        </Placeholder>
      ) : (
        <InnerBlocks />
      ) }
    </div>
  );
},

// 保存函数 (save)
save: (props) => {
  return (
    <div className={props.className}>
      <InnerBlocks.Content />
    </div>
  );
},

在这个例子中,如果 InnerBlocks 没有包含任何子区块,则显示一个自定义占位符,提示用户添加内容。

5. 最佳实践

  • 明确定义 allowedBlocks: 避免允许所有区块,明确指定允许的区块类型,可以提高用户体验和布局的稳定性。
  • 合理使用 templatetemplateLock: template 属性可以提供初始内容,templateLock 属性可以防止用户意外修改布局。
  • 使用 useInnerBlocksProps Hook 进行更精细的控制: useInnerBlocksProps Hook 允许你自定义 InnerBlocks 的行为和属性。
  • 添加清晰的占位符: 使用 Placeholder 组件可以提示用户添加内容,提高用户体验。
  • 测试不同场景: 确保 InnerBlocks 在各种场景下都能正常工作,例如不同的屏幕尺寸和不同的浏览器。

6. 常见问题

  • InnerBlocks 不显示: 检查 editsave 函数是否正确使用了 InnerBlocks 组件。
  • 无法添加区块: 检查 allowedBlocks 属性是否正确配置。
  • 模板被意外修改: 使用 templateLock 属性锁定模板。
  • 样式不正确: 确保添加了正确的 CSS 样式。

7. 总结:灵活的内容布局构建方法

InnerBlocks 组件是 Gutenberg 区块开发中构建灵活内容布局的关键工具。通过合理使用 allowedBlockstemplatetemplateLockuseInnerBlocksProps Hook,我们可以创建出各种复杂的布局,并提供良好的用户体验。理解和掌握 InnerBlocks 的用法,可以极大地提高 Gutenberg 区块开发的效率和灵活性。

发表回复

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