title: >- Композиция блоков (useInnerBlocksProps), Block Hooks API и Synced Patterns с Overrides description: >- Архитектура составных контейнеров и дизайн-систем в WordPress 7.0+ (PHP 8.4+, TypeScript 5.8): бесшовное слияние DOM через useInnerBlocksProps, блокировка структуры templateLock ('contentOnly'), автоматическое внедрение блоков через Block Hooks API и синхронные паттерны с переопределением контента. module: gutenberg-blocks order: 4 difficulty: advanced estimatedTime: 40 мин topics:
Попытка построить крупный корпоративный сайт на атомарных монолитных блоках (где у одной секции «Тарифная сетка» есть 45 плоских атрибутов в block.json: plan1Title, plan1Price, plan1Feature1, plan2Title...) быстро превращает поддержку в кошмар. Вместо перегрузки одного блока десятками полей современная архитектура Gutenberg в WordPress 7.0+ («Armstrong») опирается на Иерархическую Композицию Узлов (Compound Block Pattern).
Сложная секция разбивается на Родительский Контейнер (agentic-lab/bento-grid) и Строго Ограниченные Дочерние Блоки (agentic-lab/bento-cell). При этом в стандарте 2026 года ушли в прошлое три главные болезни раннего Gutenberg:
div-обёртки в редакторе (Wrapper Div Soup): старый компонент <InnerBlocks /> вставлял между родителем и детьми служебные div.block-editor-inner-blocks > div.block-editor-block-list__layout, из-за чего вёрстка на CSS Grid и Flexbox в админке разваливалась и требовала костыльных CSS-селекторов. Современный хук useInnerBlocksProps() сливает свойства контейнера (useBlockProps) и список детей напрямую в один чистый семантический тег (<ul {...innerBlocksProps}>).templateLock: "contentOnly" и Synced Pattern Overrides (core/pattern-overrides) позволяют глобально зафиксировать сетку, отступы и дизайн секции в одном месте, оставив контент-менеджерам доступ исключительно к редактированию текста заголовков, картинок и ссылок.hooked_blocks) позволяет плагину декларативно «пристегнуть» свой блок (например, кнопку лайка, блок автора или бейдж верификации) сразу после core/post-title или внутрь core/navigation во всех блочных темах без единой правки базы данных.Архитектура композитных блоков Gutenberg в WordPress 7.0+ основана на дереве составных компонентов (Compound Component Pattern) с декларативным разграничением ответственности между родительским контейнером и дочерними узлами:
useInnerBlocksProps): определяет топологию сетки (CSS Grid/Flexbox), транслирует контекст данных дочерним элементам (providesContext) и валидирует доступные типы блоков (allowedBlocks).parent / ancestor): гарантируют целостность иерархии, исключая появление специализированных узлов вне контекста родительского контейнера.templateLock: "contentOnly"): инкапсулирует структурную разметку дизайн-системы, изолируя свойства геометрии и компоновки от мутаций на уровне контентного редактирования.Block Hooks API): подключает зависимые динамические узлы к якорным блокам на этапе компиляции дерева шаблона без прямого изменения структуры в БД.┌─────────────────────────────────────────────────────────────────────────┐
│ РОДИТЕЛЬ: agentic-lab/bento-grid (useInnerBlocksProps -> <div.grid>) │
│ • providesContext: { "agentic-lab/gridColumns": 3 } │
│ • allowedBlocks: ["agentic-lab/bento-cell"] │
│ • templateLock: "contentOnly" (или "insert" / false) │
│ │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────┐ │
│ │ ДОЧЕРНИЙ: agentic-lab/bento-cell│ │ ДОЧЕРНИЙ: agentic/bento-cell│ │
│ │ • parent: ["agentic/bento-grid"]│ │ • usesContext: │ │
│ │ • Прямой потомок CSS Grid! │ │ ["agentic/gridColumns"] │ │
│ └─────────────────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘Хук объединяет useBlockProps и контейнер дочерних блоков в один DOM-узел, обеспечивая 100% идентичность CSS Grid в редакторе и на фронтенде.
Ядро валидирует иерархию вставки: дочерний узел виден в инсертере только тогда, когда фокус находится внутри разрешённого предка.
Режим contentOnly скрывает технические блоки-обёртки из дерева слоёв и разрешает редактировать только помеченные текстовые и медиа-узлы.
На этапе рендеринга шаблона ядро автоматически внедряет хук-блоки до/после целевых якорных блоков (например, после core/post-content).
В block.json контейнера объявите providesContext, а в block.json дочерней ячейки — parent: ['namespace/container'] и usesContext.
Соедините useBlockProps и useInnerBlocksProps на одном семантическом теге с указанием allowedBlocks, template и orientation.
Активируйте режим contentOnly или Pattern Overrides (metadata.bindings.content -> core/pattern-overrides) для безопасной передачи блока контент-команде.
Если компонент должен присутствовать во всех шаблонах блочной темы автоматически, зарегистрируйте его через hooked_blocks в block.json.
Почему для реализации CSS Grid и Flexbox контейнеров в Gutenberg необходимо использовать useInnerBlocksProps вместо устаревшего компонента InnerBlocks?
В инженерной практике Fullstack Agentic Lab связка useInnerBlocksProps, Block Hooks API и Synced Patterns применяется для:
useInnerBlocksProps карточки bento-cell рендерятся как прямые потомки <div class="agentic-bento-grid"> и в редакторе, и на фронтенде, позволяя использовать grid-column: span 2 без взлома внутренней разметки Gutenberg.Block Hooks API): подключение блока «Время чтения и AI-резюме статьи» строго после core/post-title во всех шаблонах single.html одной декларацией в block.json и фильтром в PHP 8.4+.register_block_pattern) с режимом templateLock: "contentOnly" и Synced Pattern Overrides.Сравним устаревшую реализацию вложенных блоков через <InnerBlocks /> и современную архитектуру WordPress 7.0+:
Ниже представлен комплексный производственный артефакт для WordPress 7.0+ (TypeScript 5.8 + PHP 8.4+). В первой части реализован контейнер Bento Grid на базе useInnerBlocksProps() с горизонтальной ориентацией и блокировкой contentOnly. Во второй части реализован серверный регистратор Block Hooks API и корпоративного паттерна с поддержкой Pattern Overrides (core/pattern-overrides).
/**
* ЧАСТЬ 1: Контейнер Bento Grid на базе useInnerBlocksProps (TypeScript 5.8)
* Файл: src/blocks/bento-grid/index.tsx
*/
import { registerBlockType, type TemplateArray } from '@wordpress/blocks';
import {
useBlockProps,
useInnerBlocksProps,
InspectorControls,
} from '@wordpress/block-editor';
import { PanelBody, RangeControl, SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
interface BentoGridAttributes {
columns: number;
lockMode: '' | 'all' | 'insert' | 'contentOnly';
}
// Разрешаем вставлять внутрь сетки ТОЛЬКО специализированные ячейки Bento Cell
const ALLOWED_CHILD_BLOCKS = ['agentic-lab/bento-cell'];
// Стартовый шаблон из 3 ячеек при добавлении сетки на страницу
const DEFAULT_BENTO_TEMPLATE: TemplateArray = [
[
'agentic-lab/bento-cell',
{
spanColumns: 2,
badge: 'WASM RUNTIME',
heading: 'PHP 8.4 непосредственно в браузере',
summary: 'Изолированное исполнение WordPress без серверного RTT.',
},
],
[
'agentic-lab/bento-cell',
{
spanColumns: 1,
badge: 'DUAL-LLM GATEWAY',
heading: 'Gemini 2.5 + Claude 3.5',
summary: 'Строгая валидация ответов по контрактам Zod.',
},
],
[
'agentic-lab/bento-cell',
{
spanColumns: 3,
badge: 'EDGE ISR',
heading: 'Мгновенная инвалидация кэша через HMAC-SHA256 Webhooks',
summary: 'Время отклика P95 < 18ms на глобальной сети доставки.',
},
],
];
registerBlockType<BentoGridAttributes>('agentic-lab/bento-grid', {
apiVersion: 3,
title: __('Agentic Bento Architecture Grid', 'agentic-lab'),
category: 'agentic-lab-architecture',
icon: 'grid-view',
edit({ attributes, setAttributes }) {
const { columns, lockMode } = attributes;
// 1. Формируем базовые пропсы родительского CSS-Grid узла
const blockProps = useBlockProps({
className: `agentic-bento-grid has-${columns}-columns`,
style: {
display: 'grid',
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
gap: '1.5rem',
},
});
// 2. Сливаем InnerBlocks напрямую в наш <section> без единого лишнего <div>!
const innerBlocksProps = useInnerBlocksProps(blockProps, {
allowedBlocks: ALLOWED_CHILD_BLOCKS,
template: DEFAULT_BENTO_TEMPLATE,
templateLock: lockMode === '' ? false : lockMode,
orientation: 'horizontal', // Горизонтальные стрелки перемещения для грида
});
return (
<>
<InspectorControls>
<PanelBody title={__('Архитектура Bento-сетки', 'agentic-lab')} initialOpen={true}>
<RangeControl
label={__('Количество колонок Grid', 'agentic-lab')}
value={columns}
min={1}
max={4}
onChange={(val) => setAttributes({ columns: val ?? 3 })}
/>
<SelectControl
label={__('Режим защиты макета (templateLock)', 'agentic-lab')}
value={lockMode}
options={[
{ label: 'Content Only (Только тексты — Рекомендуется)', value: 'contentOnly' },
{ label: 'Lock Insert (Разрешить только смену порядка)', value: 'insert' },
{ label: 'Lock All (Полная блокировка структуры)', value: 'all' },
{ label: 'Unlocked (Полная свобода для архитектора)', value: '' },
]}
onChange={(val) =>
setAttributes({ lockMode: val as BentoGridAttributes['lockMode'] })
}
/>
</PanelBody>
</InspectorControls>
{/* Семантический <section>, чьими ПРЯМЫМИ детьми являются ячейки bento-cell */}
<section {...innerBlocksProps} />
</>
);
},
save({ attributes }) {
const { columns } = attributes;
const blockProps = useBlockProps.save({
className: `agentic-bento-grid has-${columns}-columns`,
style: {
display: 'grid',
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
gap: '1.5rem',
},
});
// Бесшовная сериализация дочерних блоков внутрь <section>
const innerBlocksProps = useInnerBlocksProps.save(blockProps);
return <section {...innerBlocksProps} />;
},
});
/*
* ============================================================================
* ЧАСТЬ 2: WordPress 7.0+ (PHP 8.4+) — Block Hooks API & Synced Pattern Overrides
* Файл: src/Blocks/BlockHooksAndPatternsOrchestrator.php
* ============================================================================
*
* <?php
* declare(strict_types=1);
*
* namespace AgenticLab\Blocks;
*
* use WP_Block_Template;
*
* final readonly class BlockHooksAndPatternsOrchestrator
* {
* public function registerHooks(): void
* {
* // 1. Автоматически пристёгиваем блок телеметрии сразу после заголовка статьи во всех шаблонах FSE
* add_filter('hooked_block_types', $this->injectTelemetryBadgeAfterTitle(...), 10, 4);
*
* // 2. Настраиваем атрибуты автоматически вставленного хук-блока
* add_filter('hooked_block_agentic-lab/telemetry-card', $this->configureHookedBlock(...), 10, 5);
*
* // 3. Регистрируем корпоративный паттерн с поддержкой Pattern Overrides (core/pattern-overrides)
* add_action('init', $this->registerEnterprisePatterns(...));
* }
*
* public function injectTelemetryBadgeAfterTitle(
* array $hookedBlocks,
* string $position,
* string $anchorBlock,
* WP_Block_Template|array $context
* ): array {
* if ($anchorBlock === 'core/post-title' && $position === 'after') {
* $hookedBlocks[] = 'agentic-lab/telemetry-card';
* }
* return $hookedBlocks;
* }
*
* public function configureHookedBlock(
* ?array $parsedHookedBlock,
* string $hookedBlockType,
* string $relativePosition,
* array $parsedAnchorBlock,
* WP_Block_Template|array $context
* ): ?array {
* if ($parsedHookedBlock === null) {
* return null;
* }
* $parsedHookedBlock['attrs']['statusLevel'] = 'optimal';
* return $parsedHookedBlock;
* }
*
* public function registerEnterprisePatterns(): void
* {
* register_block_pattern('agentic-lab/verified-architecture-hero', [
* 'title' => 'Fullstack Agentic Lab — Hero с переопределяемыми слотами',
* 'categories' => ['featured', 'banner'],
* 'content' => '<!-- wp:group {"templateLock":"contentOnly","layout":{"type":"constrained"}} -->
* <div class="wp-block-group">
* <!-- wp:heading {"metadata":{"name":"Hero Title","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
* <h2 class="wp-block-heading">Эталонная архитектура системы</h2>
* <!-- /wp:heading -->
* <!-- wp:paragraph {"metadata":{"name":"Hero Summary","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
* <p>Описание инженерного решения с синхронизацией дизайна по всему сайту.</p>
* <!-- /wp:paragraph -->
* </div>
* <!-- /wp:group -->',
* ]);
* }
* }
*/
Команда разработки создала контейнер сетки с allowedBlocks: ['my/cell']. Однако редакторы всё равно могут вставлять блок my/cell в корень статьи через глобальный инсертер. Какая архитектурная мера предотвратит эту ошибку?