Получение атрибутов InnerBlocks и сохранение их в родительский

Я создал блок «Панели с вкладками» (с вкладками), который по сути является просто Компонент InnerBlocks, разрешающий только блок «Панель». Когда вы создаете панель, вы должны дать ей заголовок, который затем будет использоваться на панели, а также на кнопке Tab. Поэтому в моей функции рендеринга для панелей с вкладками мне нужно вытащить заголовок из дочерних блоков Panel.

Есть несколько подходов, которые я мог бы использовать, например, просто использование регулярного выражения в функции tabbed-panel-render.php для поиска в дочернем HTML-коде правильных атрибутов, но это не кажется лучшим подходом.

Я думаю, что самым простым решением было бы прослушивать любые изменения в блоках Panel и сохранять изменения (в данном случае заголовок и идентификатор) в родительском элементе. Мой текущий подход основан на этом обсуждении, в котором используются ловушки для отслеживания изменений. Кажется, эта часть работает нормально, но мне нужно где-то сохранить результат, поэтому я сохраняю их как атрибуты в блоке Tabbed Panels. Поначалу кажется, что это работает нормально, но размещение метода "setAttributes" непосредственно в функции редактирования приводит к проблемам. Если на странице слишком много блоков панели с вкладками, React выдает ошибку «слишком много отрисовок».

Где должна находиться моя функция "setAttributes" или есть лучший подход к передаче данных от дочернего к родительскому? Я думал об использовании хука useDispatch в дочернем элементе, но мне нужно проверить много событий (заголовок меняется, блок переупорядочивается, блок удаляется и т. Д.)

Вот соответствующие файлы js и php. Есть несколько настраиваемых элементов, но вы сможете их проанализировать.

tabbed-panel.js

import { arraysMatch } from 'Components/utils.js'
const { InnerBlocks } = wp.blockEditor
const { createBlock } = wp.blocks
const { Button } = wp.components
const { useDispatch, useSelect } = wp.data
const { __ } = wp.i18n

export const tabbedPanels = {
  name: 'my/tabbed-panels',
  args: {
    title: __('Tabbed Panels', '_ws'),
    description: __('Tabbable panels of content.', '_ws'),
    icon: 'table-row-after',
    category: 'common',
    supports: {
      anchor: true
    },
    attributes: {
      headings: {
        type: 'array',
        default: []
      },
      uids: {
        type: 'array',
        default: []
      }
    },
    edit: props => {
      const { setAttributes } = props
      const { headings, uids } = props.attributes
      const { insertBlock } = useDispatch('core/block-editor')
      const { panelHeadings, panelUids, blockCount } = useSelect(select => {
        const blocks = select('core/block-editor').getBlocks(props.clientId)
        return {
          panelHeadings: blocks.map(b => b.attributes.heading),
          panelUids: blocks.map(b => b.clientId),
          blockCount: select('core/block-editor').getBlockOrder(props.clientId).length
        }
      })
      if (!arraysMatch(panelHeadings, headings)) {
        setAttributes({ headings: panelHeadings })
      }
      if (!arraysMatch(panelUids, uids)) {
        setAttributes({ uids: panelUids })
      }
      return (
        <div className="block-row">
          <InnerBlocks
            allowedBlocks={ ['my/panel'] }
            templateLock={ false }
            renderAppender={ () => (
              <Button
                isSecondary
                onClick={ e => {
                  insertBlock(createBlock('my/panel'), blockCount, props.clientId)
                } }
              >
                { __('Add Panel', '_ws') }
              </Button>
            ) }
          />
        </div>
      )
    },
    save: props => {
      return (
        <InnerBlocks.Content />
      )
    }
  }
}

вкладки-панели-render.php

<?php
function block_tabbed_panels($atts, $content) {
  $atts['className'] = 'wp-block-ws-tabbed-panels ' . ($atts['className'] ?? '');
  $headings = $atts['headings'] ?? '';
  $uids = $atts['uids'] ?? '';
  ob_start(); ?>
    <div class="tabs" role="tablist">
      <?php
      foreach ($headings as $i=>$heading) : ?>
        <button
          id="tab-<?= $uids[$i]; ?>"
          class="tab"
          role="tab"
          aria-selected="false"
          aria-controls="panel-<?= $uids[$i]; ?>"
          tabindex="-1"
        >
          <?= $heading; ?>
        </button>
        <?php
      endforeach; ?>
    </div>
    <div class="panels">
      <?= $content; ?>
    </div>
  <?php
  return ob_get_clean();
}

panel.js

import ComponentHooks from 'Components/component-hooks.js'
const { InnerBlocks, RichText } = wp.blockEditor
const { __ } = wp.i18n

export const panel = {
  name: 'my/panel',
  args: {
    title: __('Panel', '_ws'),
    description: __('Panel with associated tab.', '_ws'),
    icon: 'format-aside',
    category: 'common',
    supports: {
      customClassName: false,
      html: false,
      inserter: false,
      reusable: false
    },
    attributes: {
      heading: {
        type: 'string'
      },
      uid: {
        type: 'string'
      }
    },
    edit: props => {
      const { setAttributes } = props
      const { heading } = props.attributes
      return (
        <>
          <ComponentHooks
            componentDidMount={ () => setAttributes({ uid: props.clientId }) }
          />
          <RichText
            label={ __('Tab Name', '_ws') }
            placeholder={ __('Tab Name', '_ws') }
            tagName="h4"
            onChange={ newValue => setAttributes({ heading: newValue }) }
            value={ heading }
          />
          <InnerBlocks
            templateLock={ false }
          />
        </>
      )
    },
    save: props => {
      return (
        <InnerBlocks.Content />
      )
    }
  }
}

панель-render.php

<?php
function block_panel($atts, $content) {
  $uid = $atts['uid'] ?? '';
  ob_start(); ?>
    <div
      id="panel-<?= $uid ?>"
      class="panel"
      role="tabpanel"
      aria-labelledby="tab-<?= $uid; ?>"
      tabindex="0"
      hidden="hidden"
    >
      <?= $content; ?>
    </div>
  <?php
  return ob_get_clean();
}

person Rice_Crisp    schedule 23.01.2020    source источник


Ответы (1)


Вы можете получить доступ к дочерним блокам от родителя и получить атрибуты (заголовок вкладки в вашем случае).

componentDidUpdate(previousProps, previousState) {
    var myID = this.props.clientId;
    var tabs_title = [];
    this.myBlock = wp.data.select('core/block-editor').getBlock(myID);
    this.myBlock.innerBlocks.map(block => {
         tabs_title.push( block.attributes.title );
    });
    this.props.setAttributes({ 'tabs_title': tabs_title });
}
person Albin    schedule 08.04.2020
comment
В этом подходе есть проблема. setAttributes запускает componentDidUpdate, и это приводит к бесконечному циклу. - person Sudar; 30.05.2020
comment
Да, но вы можете проверить с помощью lodash.isEqual, изменились ли previousProps vs props.attributes, или previousState vs state, или оба или ни один из них. - person Albin; 31.05.2020