You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义Gutenberg块属性保存后丢失问题求助

Gutenberg自定义媒体块属性丢失与渲染不匹配问题修复

问题核心原因

  1. 属性定义错误:mediaType字段重复声明selector,导致Gutenberg无法正确从DOM提取该属性;save.js输出的DOM缺少data-id属性,使得mediaID无法被解析,进而整个mediaItems数组加载失败。
  2. 编辑器逻辑冲突:用单个mediaID属性处理多选媒体场景,导致媒体选择、预览和移除逻辑混乱;withSelect仅获取单个媒体数据,不符合多选需求。
  3. 编辑器与保存DOM不一致:edit.js渲染的媒体元素未添加data-id和data-type属性,导致编辑器生成的DOM与save.js输出的DOM不匹配,触发post-body校验错误。

修复步骤

1. 修正属性定义

移除mediaType字段中多余的selector,确保query能正确解析每个媒体项的属性:

"attributes": {
    "mediaItems": {
        "type": "array",
        "source": "query",
        "selector": ".instagram-media",
        "default": [],
        "query": {
            "mediaID": {
                "type": "number",
                "source": "attribute",
                "attribute": "data-id"
            },
            "mediaType": {
                "type": "string",
                "source": "attribute",
                "attribute": "data-type"
            },
            "mediaURL": {
                "type": "string",
                "source": "attribute",
                "attribute": "src"
            },
            "mediaAlt": {
                "type": "string",
                "source": "attribute",
                "attribute": "alt"
            }
        }
    }
}

2. 重构edit.js逻辑

  • 移除冗余的mediaID属性,改用mediaItems数组处理多选媒体
  • 更新媒体选择、预览和移除逻辑,适配多选场景
  • 在编辑器渲染的媒体元素上添加data-id和data-type,确保与save输出DOM一致
  • 移除无用的withSelect包装,因为多选场景无需单独获取单个媒体详情

修改后的关键代码:

import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls, MediaUploadCheck, MediaUpload } from '@wordpress/block-editor';
import { PanelBody, Button, ResponsiveWrapper } from '@wordpress/components';
import { Component, Fragment } from '@wordpress/element';
import './editor.scss';

const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ];

class mediaSelectorEdit extends Component {
    render() {
        const { attributes, setAttributes } = this.props;
        const { mediaItems } = attributes;
        const instructions = <p>{ __( 'To edit the Post Mediadaten, you need permission to upload media.', 'post-media-selector' ) }</p>;
        const className = "wp-block-" + this.props.name.replace("/", "-")

        const onUpdatemedia = ( selectedMedia ) => {
            setAttributes( {
                mediaItems: selectedMedia.map( item => ({
                    mediaID: parseInt( item.id, 10 ),
                    mediaType: item.type,
                    mediaURL: item.url,
                    mediaAlt: item.alt || ''
                }) ),
            } );
        };

        const onRemovemedia = () => {
            setAttributes( { mediaItems: [] } );
        };

        return (
            <Fragment>
                <InspectorControls>
                    <PanelBody
                        title={ __( 'Post Mediadaten Einstellung', 'post-media-selector' ) }
                        initialOpen={ true }
                    >
                        <div className="wp-block-post-media-selector-media">
                            <MediaUploadCheck fallback={ instructions }>
                                <MediaUpload
                                    title={ __( 'Post Mediadaten', 'post-media-selector' ) }
                                    onSelect={ onUpdatemedia }
                                    allowedTypes={ ALLOWED_MEDIA_TYPES }
                                    value={ mediaItems.map(item => item.mediaID) }
                                    multiple={ true }
                                    render={ ( { open } ) => (
                                        <Button
                                            className={ !mediaItems.length ? 'editor-post-featured-media__toggle' : 'editor-post-featured-media__preview' }
                                            onClick={ open }>
                                            { !mediaItems.length && ( __( 'Post Mediadaten hochladen', 'post-media-selector' ) ) }
                                            { mediaItems.length > 0 && (
                                                <div>
                                                    {mediaItems.slice(0,1).map(item => (
                                                        <ResponsiveWrapper
                                                            key={item.mediaID}
                                                            naturalWidth={ item.mediaType === 'image' ? item.media_details?.width || 640 : 640 }
                                                            naturalHeight={ item.mediaType === 'image' ? item.media_details?.height || 480 : 360 }
                                                        >
                                                            <img src={ item.mediaURL } alt={ item.mediaAlt } />
                                                        </ResponsiveWrapper>
                                                    ))}
                                                </div>
                                            )}
                                        </Button>
                                    ) }
                                />
                            </MediaUploadCheck>
                            { mediaItems.length > 0 &&
                                <>
                                    <MediaUploadCheck>
                                        <MediaUpload
                                            title={ __( 'Post Mediadaten', 'post-media-selector' ) }
                                            onSelect={ onUpdatemedia }
                                            allowedTypes={ ALLOWED_MEDIA_TYPES }
                                            value={ mediaItems.map(item => item.mediaID) }
                                            multiple={ true }
                                            render={ ( { open } ) => (
                                                <Button onClick={ open } isDefault isLarge>
                                                    { __( 'Ersetze Post Mediadaten', 'post-media-selector' ) }
                                                </Button>
                                            ) }
                                        />
                                    </MediaUploadCheck>
                                    <MediaUploadCheck>
                                        <Button onClick={ onRemovemedia } isLink isDestructive>
                                            { __( 'Entferne Post Mediadaten', 'post-media-selector' ) }
                                        </Button>
                                    </MediaUploadCheck>
                                </>
                            }
                        </div>
                    </PanelBody>
                </InspectorControls>

                <div className={ className }>
                    <div className="instagram-post__media-container">
                        {mediaItems.map(media => {
                            if(media.mediaType === "video") {
                                return (
                                    <div key={media.mediaID} className="instagram-post__media-container__media video">
                                        <picture className="startframe"></picture>
                                        <picture className="endframe"></picture>
                                        <video 
                                            src={ media.mediaURL } 
                                            data-id={ media.mediaID }
                                            data-type={ media.mediaType }
                                            className="instagram-media"
                                        ></video>
                                    </div>
                                )
                            } else if(media.mediaType === "image") {
                                return (
                                    <picture key={media.mediaID} className="instagram-post__media-container__media image">
                                        <source srcset={media.mediaURL} />
                                        {media2XURLGenerator(media.mediaURL) &&
                                            <source srcset={`${media2XURLGenerator(media.mediaURL)} 2x`} />
                                        }
                                        <img 
                                            src={media.mediaURL} 
                                            alt={media.mediaAlt} 
                                            data-id={ media.mediaID }
                                            data-type={ media.mediaType }
                                            className="instagram-media" 
                                        />
                                    </picture>
                                )
                            }
                            return null;
                        })}
                    </div>
                </div>
            </Fragment>
        )
    }
}

export function media2XURLGenerator(mediaURL) {
    const mediaLast = mediaURL.split(".").pop()
    const urlFirst = mediaURL.replace(`.${mediaLast}`, "")

    return `${urlFirst}_2x.${mediaLast}`
}

export default mediaSelectorEdit;

3. 修正save.js输出

确保每个媒体元素都添加data-id和data-type属性,同时优化解构和空值处理:

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { media2XURLGenerator } from './edit'

export default function save( { attributes } ) {
    const blockProps = useBlockProps.save();
    const { mediaItems } = attributes;

    return (
        <div { ...blockProps }>
            <div className="instagram-post__media-container">
                {mediaItems.map(media => {
                    const { mediaID, mediaURL, mediaAlt, mediaType } = media;
                    if(mediaType === "video") {
                        return (
                            <div key={mediaID} className="instagram-post__media-container__media video">
                                <picture className="startframe"></picture>
                                <picture className="endframe"></picture>
                                <video 
                                    src={ mediaURL } 
                                    data-id={ mediaID }
                                    data-type={ mediaType }
                                    className="instagram-media"
                                ></video>
                            </div>
                        )
                    } else if(mediaType === "image") {
                        return (
                            <picture key={mediaID} className="instagram-post__media-container__media image">
                                <source srcset={mediaURL} />
                                {media2XURLGenerator(mediaURL) &&
                                    <source srcset={`${media2XURLGenerator(mediaURL)} 2x`} />
                                }
                                <img 
                                    src={mediaURL} 
                                    alt={mediaAlt || ''} 
                                    data-id={ mediaID }
                                    data-type={ mediaType }
                                    className="instagram-media" 
                                />
                            </picture>
                        )
                    }
                    return null;
                })}
            </div>
        </div>
    );
}

验证修复

完成上述修改后,重新编译块代码,测试流程:

  1. 在编辑器中添加多个图片/视频,保存帖子
  2. 刷新编辑器,确认媒体内容正常显示,属性未丢失
  3. 查看前端页面,媒体正常渲染,控制台无post-body不匹配错误

内容的提问来源于stack exchange,提问作者Andreas Markl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 06:15:32