Gutenberg区块开发:如何基于`Inspector Controls`实现高级的自定义设置面板?

Gutenberg 区块开发:基于 Inspector Controls 实现高级自定义设置面板

大家好!今天我们要深入探讨 Gutenberg 区块开发中一个非常重要的方面:如何利用 Inspector Controls 组件构建高级的自定义设置面板。Inspector Controls 是 Gutenberg 编辑器侧边栏中用于控制区块属性的关键工具,一个精心设计的 Inspector Controls 面板能够极大地提升用户体验,让他们可以轻松地定制区块的行为和外观。

我们将从基础概念入手,逐步讲解如何使用 Inspector Controls 及其相关的组件,实现各种高级的自定义设置,并提供大量的代码示例,确保你能够将其运用到实际的区块开发中。

1. Inspector Controls 基础

Inspector Controls 组件是 @wordpress/block-editor 包提供的一个 React 组件,它负责将设置控件渲染到 Gutenberg 编辑器的侧边栏中。任何放置在 Inspector Controls 组件中的控件都会自动出现在区块的设置面板里。

最基本的用法如下:

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';

function Edit( { attributes, setAttributes } ) {
    const { myCustomText } = attributes;

    return (
        <>
            <InspectorControls>
                <PanelBody title="自定义设置">
                    <TextControl
                        label="自定义文本"
                        value={ myCustomText }
                        onChange={ ( newText ) => setAttributes( { myCustomText: newText } ) }
                    />
                </PanelBody>
            </InspectorControls>
            <div>
                { myCustomText }
            </div>
        </>
    );
}

export default Edit;

这段代码展示了一个简单的 Inspector Controls 用例。它包含一个 PanelBody 组件,用于将设置分组,以及一个 TextControl 组件,允许用户输入文本。当用户在 TextControl 中输入文本时,setAttributes 函数会更新区块的 myCustomText 属性,从而改变区块的显示内容。

2. 常用组件和控件

Inspector Controls 并非孤立存在,它通常与一系列的 @wordpress/components 包提供的组件和控件一起使用,以构建丰富的用户界面。以下是一些常用的组件:

  • PanelBody: 用于将设置控件分组,提供折叠/展开功能,使设置面板更清晰易用。
  • TextControl: 用于输入单行文本。
  • TextareaControl: 用于输入多行文本。
  • SelectControl: 提供下拉选择框。
  • RadioControl: 提供单选按钮组。
  • CheckboxControl: 提供复选框。
  • RangeControl: 提供滑块控件,用于选择数值范围。
  • ColorPicker: 提供颜色选择器。
  • ToggleControl: 提供开关按钮。
  • Button: 提供按钮,用于触发特定操作。

这些组件都具有丰富的属性,可以定制它们的外观和行为。例如,RangeControl 可以设置最小值、最大值、步长等。

3. 高级自定义设置技巧

接下来,我们将介绍一些高级的自定义设置技巧,帮助你构建更强大的 Inspector Controls 面板。

3.1. 条件渲染控件

有时候,我们需要根据其他属性的值来显示或隐藏某些控件。这可以通过简单的条件判断来实现。

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, ToggleControl, TextControl } from '@wordpress/components';

function Edit( { attributes, setAttributes } ) {
    const { showAdvancedOptions, advancedText } = attributes;

    return (
        <>
            <InspectorControls>
                <PanelBody title="高级设置">
                    <ToggleControl
                        label="显示高级选项"
                        checked={ showAdvancedOptions }
                        onChange={ ( newShowAdvancedOptions ) => setAttributes( { showAdvancedOptions: newShowAdvancedOptions } ) }
                    />
                    { showAdvancedOptions && (
                        <TextControl
                            label="高级文本"
                            value={ advancedText }
                            onChange={ ( newText ) => setAttributes( { advancedText: newText } ) }
                        />
                    ) }
                </PanelBody>
            </InspectorControls>
            <div>
                { advancedText }
            </div>
        </>
    );
}

export default Edit;

在这个例子中,TextControl 只会在 showAdvancedOptions 属性为 true 时才显示。这使得用户可以根据需要选择是否显示高级设置。

3.2. 使用 useState 管理局部状态

有时候,我们需要管理一些与区块属性无关的局部状态。例如,我们可能需要记录一个临时值,用于在 Inspector Controls 面板中进行计算。在这种情况下,我们可以使用 React 的 useState Hook。

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl, Button } from '@wordpress/components';
import { useState } from '@wordpress/element';

function Edit( { attributes, setAttributes } ) {
    const { myNumber } = attributes;
    const [ tempNumber, setTempNumber ] = useState( myNumber || 0 ); // 初始化 tempNumber

    const handleUpdateNumber = () => {
        setAttributes( { myNumber: parseInt(tempNumber) } );
    };

    return (
        <>
            <InspectorControls>
                <PanelBody title="数字设置">
                    <TextControl
                        label="数字"
                        type="number"
                        value={ tempNumber }
                        onChange={ ( newNumber ) => setTempNumber( newNumber ) }
                    />
                    <Button onClick={ handleUpdateNumber } isPrimary>
                        更新数字
                    </Button>
                </PanelBody>
            </InspectorControls>
            <div>
                当前数字:{ myNumber }
            </div>
        </>
    );
}

export default Edit;

在这个例子中,tempNumber 是一个局部状态,用于临时存储用户输入的数字。当用户点击 "更新数字" 按钮时,handleUpdateNumber 函数会将 tempNumber 的值更新到区块的 myNumber 属性中。注意,tempNumber被强制转换成数字类型 parseInt(tempNumber),防止出现类型错误。

3.3. 动态生成控件

有时候,我们需要根据数据动态生成控件。例如,我们可能需要根据一个数组来生成一组 CheckboxControl

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, CheckboxControl } from '@wordpress/components';

function Edit( { attributes, setAttributes } ) {
    const { selectedOptions } = attributes;
    const options = [ 'Option 1', 'Option 2', 'Option 3' ];

    const handleOptionChange = ( option ) => {
        const newSelectedOptions = selectedOptions.includes( option )
            ? selectedOptions.filter( ( item ) => item !== option )
            : [ ...selectedOptions, option ];
        setAttributes( { selectedOptions: newSelectedOptions } );
    };

    return (
        <>
            <InspectorControls>
                <PanelBody title="选项设置">
                    { options.map( ( option ) => (
                        <CheckboxControl
                            key={ option }
                            label={ option }
                            checked={ selectedOptions.includes( option ) }
                            onChange={ () => handleOptionChange( option ) }
                        />
                    ) ) }
                </PanelBody>
            </InspectorControls>
            <div>
                选择的选项:{ selectedOptions.join( ', ' ) }
            </div>
        </>
    );
}

export default Edit;

在这个例子中,我们使用 map 函数遍历 options 数组,为每个选项生成一个 CheckboxControlhandleOptionChange 函数负责更新 selectedOptions 属性,添加或删除选中的选项。

3.4. 使用 withState 高阶组件 (不推荐,已废弃,作为参考)

注意:withState 是一个已废弃的 API,在新的 Gutenberg 版本中不再推荐使用。这里仅作为历史参考,建议使用 useState Hook 代替。

在旧版本的 Gutenberg 中,withState 高阶组件可以用于管理 Inspector Controls 中的局部状态。它可以将状态注入到组件的 props 中,并提供更新状态的函数。

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl, Button } from '@wordpress/components';
import { withState } from '@wordpress/compose';

const MyComponent = withState( { tempNumber: 0 } )( ( { attributes, setAttributes, tempNumber, setState } ) => {
    const { myNumber } = attributes;

    const handleUpdateNumber = () => {
        setAttributes( { myNumber: parseInt(tempNumber) } );
    };

    return (
        <>
            <InspectorControls>
                <PanelBody title="数字设置">
                    <TextControl
                        label="数字"
                        type="number"
                        value={ tempNumber }
                        onChange={ ( newNumber ) => setState( { tempNumber: newNumber } ) }
                    />
                    <Button onClick={ handleUpdateNumber } isPrimary>
                        更新数字
                    </Button>
                </PanelBody>
            </InspectorControls>
            <div>
                当前数字:{ myNumber }
            </div>
        </>
    );
} );

function Edit( props ) {
    return <MyComponent { ...props } />;
}

export default Edit;

在这个例子中,withState( { tempNumber: 0 } )tempNumber 状态注入到 MyComponent 中。setState 函数可以用于更新 tempNumber 的值。

3.5. 使用 useInstanceId 获取唯一 ID

在某些情况下,我们需要为控件生成唯一的 ID。例如,当我们需要将 labelinput 元素关联时,我们需要确保每个 input 元素都有唯一的 ID。可以使用 useInstanceId Hook 来生成唯一的 ID。

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';

function Edit( { attributes, setAttributes } ) {
    const { myText } = attributes;
    const instanceId = useInstanceId( Edit );
    const textControlId = `my-text-control-${ instanceId }`;

    return (
        <>
            <InspectorControls>
                <PanelBody title="文本设置">
                    <TextControl
                        label="文本"
                        id={ textControlId }
                        value={ myText }
                        onChange={ ( newText ) => setAttributes( { myText: newText } ) }
                    />
                </PanelBody>
            </InspectorControls>
            <div htmlFor={ textControlId }>
                { myText }
            </div>
        </>
    );
}

export default Edit;

在这个例子中,useInstanceId( Edit ) 会为每个 Edit 组件实例生成一个唯一的 ID。我们将这个 ID 用于构建 TextControl 的 ID。

3.6. 使用 __experimentalInputControl 实现更精细的控制

__experimentalInputControl 是一个实验性的组件,它提供对输入控件更精细的控制。它允许你自定义输入框的类型、前缀、后缀等。

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, __experimentalInputControl as InputControl } from '@wordpress/components';

function Edit( { attributes, setAttributes } ) {
    const { myNumber } = attributes;

    return (
        <>
            <InspectorControls>
                <PanelBody title="数字设置">
                    <InputControl
                        label="数字"
                        type="number"
                        value={ myNumber }
                        onChange={ ( newNumber ) => setAttributes( { myNumber: parseFloat(newNumber) || 0 } ) }
                        prefix="¥"
                        suffix=" 元"
                    />
                </PanelBody>
            </InspectorControls>
            <div>
                { myNumber }
            </div>
        </>
    );
}

export default Edit;

在这个例子中,我们使用 InputControl 组件,并设置了 prefixsuffix 属性,分别在输入框的前面和后面添加了 "¥" 和 " 元" 符号。注意这里将输入的字符串转化为浮点数parseFloat(newNumber) || 0,如果转化失败,默认值为0。

4. 实例:构建一个高级图像选择器

让我们结合上述技巧,构建一个高级的图像选择器,它允许用户选择图片,并设置图片的对齐方式、尺寸和链接。

import { InspectorControls, MediaPlaceholder } from '@wordpress/block-editor';
import { PanelBody, SelectControl, RangeControl, ToggleControl } from '@wordpress/components';
import { useState } from '@wordpress/element';

function Edit( { attributes, setAttributes } ) {
    const { imageUrl, imageAlt, alignment, imageWidth, linkToMedia } = attributes;
    const [ isEditingImage, setIsEditingImage ] = useState( false );

    const handleImageSelect = ( media ) => {
        setAttributes( {
            imageUrl: media.url,
            imageAlt: media.alt,
        } );
        setIsEditingImage( false );
    };

    const handleImageRemove = () => {
        setAttributes( {
            imageUrl: null,
            imageAlt: null,
        } );
    };

    return (
        <>
            <InspectorControls>
                <PanelBody title="图像设置">
                    { imageUrl ? (
                        <>
                            <img src={ imageUrl } alt={ imageAlt } style={ { maxWidth: '100%' } } />
                            <button onClick={ handleImageRemove }>移除图片</button>
                            <SelectControl
                                label="对齐方式"
                                value={ alignment }
                                options={ [
                                    { label: '无', value: '' },
                                    { label: '左对齐', value: 'left' },
                                    { label: '居中', value: 'center' },
                                    { label: '右对齐', value: 'right' },
                                ] }
                                onChange={ ( newAlignment ) => setAttributes( { alignment: newAlignment } ) }
                            />
                            <RangeControl
                                label="图片宽度"
                                value={ imageWidth }
                                min={ 50 }
                                max={ 500 }
                                step={ 10 }
                                onChange={ ( newWidth ) => setAttributes( { imageWidth: newWidth } ) }
                            />
                            <ToggleControl
                                label="链接到媒体文件"
                                checked={ linkToMedia }
                                onChange={ ( newLinkToMedia ) => setAttributes( { linkToMedia: newLinkToMedia } ) }
                            />

                        </>
                    ) : (
                        <MediaPlaceholder
                            onSelect={ handleImageSelect }
                            allowedTypes={ [ 'image' ] }
                            multiple={ false }
                            labels={ {
                                title: '选择图片',
                                instructions: '上传或选择媒体库中的图片',
                            } }
                            onError={ ( error ) => console.error( error ) }
                        />
                    ) }
                </PanelBody>
            </InspectorControls>
            <div style={ { textAlign: alignment } }>
                { imageUrl && (
                    <a href={ linkToMedia ? imageUrl : '#' }>
                        <img
                            src={ imageUrl }
                            alt={ imageAlt }
                            style={ { width: `${ imageWidth }px` } }
                        />
                    </a>
                ) }
            </div>
        </>
    );
}

export default Edit;

这个例子展示了如何使用 MediaPlaceholder 组件来选择图片,并使用 SelectControlRangeControlToggleControl 组件来设置图片的对齐方式、尺寸和链接。它还使用 useState Hook 来管理编辑图片的状态。

5. 表格总结常用控件属性

组件名称 常用属性 说明
PanelBody title, initialOpen, onToggle title: 面板标题;initialOpen: 初始状态是否展开;onToggle: 切换展开/折叠状态时的回调函数。
TextControl label, value, onChange, type, placeholder, help label: 标签;value: 当前值;onChange: 值改变时的回调函数;type: 输入框类型(text, email, number 等);placeholder: 占位符;help: 帮助文本。
SelectControl label, value, options, onChange, help label: 标签;value: 当前值;options: 选项数组([{ label: ‘选项1’, value: ‘value1’ }, …]);onChange: 值改变时的回调函数;help: 帮助文本。
CheckboxControl label, checked, onChange, help label: 标签;checked: 是否选中;onChange: 值改变时的回调函数;help: 帮助文本。
RangeControl label, value, onChange, min, max, step, allowReset, withInputField label: 标签;value: 当前值;onChange: 值改变时的回调函数;min: 最小值;max: 最大值;step: 步长;allowReset: 是否显示重置按钮;withInputField: 是否显示输入框。
ColorPicker color, onChange, enableAlpha color: 当前颜色;onChange: 颜色改变时的回调函数;enableAlpha: 是否启用透明度。
ToggleControl label, checked, onChange, help label: 标签;checked: 是否选中;onChange: 值改变时的回调函数;help: 帮助文本。
Button isPrimary, isSecondary, isSmall, isLarge, onClick, children isPrimary: 是否是主要按钮;isSecondary: 是否是次要按钮;isSmall: 是否是小按钮;isLarge: 是否是大按钮;onClick: 点击事件回调函数;children: 按钮文本。
__experimentalInputControl label, value, onChange, type, prefix, suffix, min, max, step label: 标签;value: 当前值;onChange: 值改变时的回调函数;type: 输入框类型(text, email, number 等);prefix: 前缀;suffix: 后缀;min: 最小值;max: 最大值;step: 步长。

总结:构建更强大的区块设置面板

通过学习 Inspector Controls 的基础知识,以及各种高级技巧和组件,我们能够构建出更强大、更灵活的 Gutenberg 区块设置面板。掌握这些技术,将极大地提升你的区块开发的效率和用户体验。

希望今天的讲解能够帮助你更好地理解和使用 Inspector Controls,并在实际的区块开发中发挥它的强大功能。记住,不断实践和探索是掌握任何技术的关键。祝你在 Gutenberg 区块开发的道路上越走越远!

发表回复

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