Gutenberg 区块:利用 InnerBlocks
组件构建灵活的内容布局
大家好!今天我们来深入探讨 Gutenberg 区块开发中一个非常强大的组件:InnerBlocks
。它允许我们在自定义区块内部嵌套其他区块,从而构建出极其灵活和可复用的内容布局。我们将从基础概念入手,逐步深入到高级用法,并通过丰富的代码示例来帮助大家理解。
1. InnerBlocks
基础:嵌套区块的容器
InnerBlocks
本质上是一个区块容器。它允许你在父区块的编辑界面中插入、移动、删除其他区块,这些区块被称为子区块。父区块负责管理和控制子区块的整体布局,而子区块则负责各自的内容和样式。
1.1 基本用法:允许所有区块
最简单的用法是允许 InnerBlocks
包含所有已注册的区块。这可以通过在父区块的 edit
和 save
函数中使用 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
提供初始内容。它接受一个区块数组,每个区块都包含 name
和 attributes
属性。
// 编辑函数 (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
的模板,防止用户添加、删除或移动区块。它可以设置为 all
或 insert
。
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
: 避免允许所有区块,明确指定允许的区块类型,可以提高用户体验和布局的稳定性。 - 合理使用
template
和templateLock
:template
属性可以提供初始内容,templateLock
属性可以防止用户意外修改布局。 - 使用
useInnerBlocksProps
Hook 进行更精细的控制:useInnerBlocksProps
Hook 允许你自定义InnerBlocks
的行为和属性。 - 添加清晰的占位符: 使用
Placeholder
组件可以提示用户添加内容,提高用户体验。 - 测试不同场景: 确保
InnerBlocks
在各种场景下都能正常工作,例如不同的屏幕尺寸和不同的浏览器。
6. 常见问题
InnerBlocks
不显示: 检查edit
和save
函数是否正确使用了InnerBlocks
组件。- 无法添加区块: 检查
allowedBlocks
属性是否正确配置。 - 模板被意外修改: 使用
templateLock
属性锁定模板。 - 样式不正确: 确保添加了正确的 CSS 样式。
7. 总结:灵活的内容布局构建方法
InnerBlocks
组件是 Gutenberg 区块开发中构建灵活内容布局的关键工具。通过合理使用 allowedBlocks
、template
、templateLock
和 useInnerBlocksProps
Hook,我们可以创建出各种复杂的布局,并提供良好的用户体验。理解和掌握 InnerBlocks
的用法,可以极大地提高 Gutenberg 区块开发的效率和灵活性。