获取 InnerBlocks 的属性并将它们保存到 parent

Getting attributes of InnerBlocks and saving them to parent

我创建了一个 "Tabbed Panels" (tabbed content) 块,它本质上只是一个仅允许块 "Panel" 的 InnerBlocks 组件。当您创建一个面板时,您必须给面板一个标题,然后在面板中使用该标题以及 Tab 按钮。因此,在选项卡式面板的渲染函数中,我需要从 children 面板块中提取标题。

我可以使用几种方法,比如在 tabbed-panels-render.php 函数中使用正则表达式来搜索 children html 的正确属性,但这似乎不是最好的方法。

我认为最简单的解决方案是监听面板块的任何更改并将更改(在本例中为标题和 ID)保存到 parent。我当前的方法基于 this discussion,它使用钩子来监听变化。该部分似乎工作正常,但我需要将输出保存在某处,因此我将它们作为属性保存到选项卡面板块。起初这似乎工作正常,但将 "setAttributes" 方法直接放在编辑功能中会导致问题。如果页面上有太多选项卡式面板块,则 React 会抛出 "too many renders" 错误。

我的 "setAttributes" 函数应该放在哪里,或者是否有更好的方法将数据从 child 传递到 parent?我想过在 child 中使用 useDispatch 钩子,但我需要检查很多事件(标题更改、块重新排序、块被删除等)

这里是相关的js和php文件。有一些自定义元素,但你应该可以解析它。

tabbed-panels.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 />
      )
    }
  }
}

tabbed-panels-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 />
      )
    }
  }
}

panel-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();
}

您可以从 parent 访问 children 块并获取属性(在您的情况下为选项卡标题)。

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 });
}