自定义Gutenberg块属性保存后丢失问题求助
Gutenberg自定义媒体块属性丢失与渲染不匹配问题修复
问题核心原因
- 属性定义错误:
mediaType字段重复声明selector,导致Gutenberg无法正确从DOM提取该属性;save.js输出的DOM缺少data-id属性,使得mediaID无法被解析,进而整个mediaItems数组加载失败。 - 编辑器逻辑冲突:用单个
mediaID属性处理多选媒体场景,导致媒体选择、预览和移除逻辑混乱;withSelect仅获取单个媒体数据,不符合多选需求。 - 编辑器与保存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> ); }
验证修复
完成上述修改后,重新编译块代码,测试流程:
- 在编辑器中添加多个图片/视频,保存帖子
- 刷新编辑器,确认媒体内容正常显示,属性未丢失
- 查看前端页面,媒体正常渲染,控制台无
post-body不匹配错误
内容的提问来源于stack exchange,提问作者Andreas Markl
相关产品推荐
相关产品推荐

