Gutenberg区块:如何利用`MediaUpload`组件处理媒体文件上传,并实现自定义文件验证?

Gutenberg 区块:利用 MediaUpload 组件处理媒体文件上传与自定义验证

大家好,今天我们深入探讨 Gutenberg 区块开发中一个至关重要的环节:如何利用 WordPress 提供的 MediaUpload 组件来处理媒体文件的上传,并在此基础上实现自定义的文件验证机制。我们将从 MediaUpload 组件的基本使用方法入手,逐步深入到自定义验证的实现细节,最终构建一个健壮且用户体验良好的媒体上传区块。

MediaUpload 组件:基本用法与核心属性

MediaUpload 组件是 @wordpress/block-editor 包提供的,它封装了 WordPress 媒体库的核心上传逻辑,简化了开发者在 Gutenberg 区块中集成媒体上传功能的流程。

首先,我们需要从 @wordpress/block-editor 包中引入 MediaUpload 组件:

import { MediaUpload } from '@wordpress/block-editor';

MediaUpload 组件的基本用法如下:

<MediaUpload
    onSelect={ ( media ) => console.log( media ) }
    allowedTypes={ [ 'image' ] }
    render={ ( { open } ) => (
        <button onClick={ open }>
            选择图片
        </button>
    ) }
/>

让我们逐一解析这段代码:

  • onSelect: 这是一个必需的属性,它接受一个函数作为参数。当用户成功从媒体库选择或上传文件后,该函数会被调用。media 参数是一个包含所选媒体信息的对象,例如 id, url, alt 等。
  • allowedTypes: 这是一个可选属性,用于限制允许上传的文件类型。它接受一个字符串数组作为参数,例如 [ 'image' ], [ 'image', 'video' ], [ 'application/pdf' ]。如果省略此属性,则允许上传所有类型的文件。
  • render: 这是一个必需的属性,它接受一个函数作为参数。该函数负责渲染触发媒体库弹窗的 UI 元素。该函数的参数是一个对象,其中包含一个 open 属性,该属性是一个函数,调用它会打开媒体库弹窗。

除了上述三个核心属性,MediaUpload 组件还提供了其他一些常用的属性,例如:

属性名 类型 描述
multiple boolean 是否允许多选文件,默认为 false
gallery boolean 是否启用图片库模式,仅适用于 allowedTypes 包含 'image' 时。
value number 当前已选择的媒体 ID。
title string 媒体库弹窗的标题。
buttonText string 按钮的文本,默认为 "选择媒体"。
onError function 上传或选择文件发生错误时调用的函数。
accept string HTML input元素的 accept 属性,用于进一步限制允许上传的文件类型,例如 image/jpeg,image/png

实现一个简单的图片上传区块

现在,我们将结合 MediaUpload 组件,创建一个简单的图片上传区块。该区块将允许用户上传一张图片,并在区块编辑器中预览该图片。

首先,我们需要注册一个区块:

import { registerBlockType } from '@wordpress/blocks';
import { MediaUpload } from '@wordpress/block-editor';

registerBlockType( 'my-plugin/image-upload-block', {
    title: '图片上传区块',
    icon: 'format-image',
    category: 'common',
    attributes: {
        imageId: {
            type: 'number',
            default: null,
        },
        imageUrl: {
            type: 'string',
            default: '',
        },
        imageAlt: {
            type: 'string',
            default: '',
        },
    },
    edit: ( { attributes, setAttributes } ) => {
        const { imageId, imageUrl, imageAlt } = attributes;

        const onSelectImage = ( media ) => {
            setAttributes( {
                imageId: media.id,
                imageUrl: media.url,
                imageAlt: media.alt,
            } );
        };

        return (
            <div>
                { imageUrl ? (
                    <img src={ imageUrl } alt={ imageAlt } style={ { maxWidth: '100%' } } />
                ) : (
                    <MediaUpload
                        onSelect={ onSelectImage }
                        allowedTypes={ [ 'image' ] }
                        value={ imageId }
                        render={ ( { open } ) => (
                            <button onClick={ open }>
                                选择图片
                            </button>
                        ) }
                    />
                ) }
            </div>
        );
    },
    save: ( { attributes } ) => {
        const { imageUrl, imageAlt } = attributes;

        return imageUrl ? (
            <img src={ imageUrl } alt={ imageAlt } />
        ) : null;
    },
} );

这段代码定义了一个名为 my-plugin/image-upload-block 的区块,它包含以下属性:

  • imageId: 存储已上传图片的 ID。
  • imageUrl: 存储已上传图片的 URL。
  • imageAlt: 存储已上传图片的 Alt 文本。

edit 函数负责渲染区块编辑器中的界面。如果 imageUrl 存在,则显示图片预览;否则,显示 MediaUpload 组件,允许用户上传或选择图片。

save 函数负责渲染前端页面的内容。如果 imageUrl 存在,则显示图片。

自定义文件验证:超越 allowedTypes 的限制

allowedTypes 属性提供了一种简单的文件类型限制方式,但它无法满足更复杂的验证需求,例如:

  • 限制文件大小。
  • 验证图片尺寸。
  • 检查文件是否包含恶意代码。

为了实现自定义的文件验证,我们需要利用 MediaUpload 组件的 onError 属性,并结合 JavaScript 的 File API。

以下是一个自定义文件大小验证的示例:

import { registerBlockType } from '@wordpress/blocks';
import { MediaUpload } from '@wordpress/block-editor';
import { useState } from '@wordpress/element';

registerBlockType( 'my-plugin/image-upload-block-with-validation', {
    title: '图片上传区块(带验证)',
    icon: 'format-image',
    category: 'common',
    attributes: {
        imageId: {
            type: 'number',
            default: null,
        },
        imageUrl: {
            type: 'string',
            default: '',
        },
        imageAlt: {
            type: 'string',
            default: '',
        },
    },
    edit: ( { attributes, setAttributes } ) => {
        const { imageId, imageUrl, imageAlt } = attributes;
        const [ errorMessage, setErrorMessage ] = useState( '' );

        const onSelectImage = ( media ) => {
            setAttributes( {
                imageId: media.id,
                imageUrl: media.url,
                imageAlt: media.alt,
            } );
            setErrorMessage( '' ); // 清除错误信息
        };

        const validateFile = ( files ) => {
            const file = files[0];
            const maxSizeInBytes = 2 * 1024 * 1024; // 2MB

            if ( file.size > maxSizeInBytes ) {
                setErrorMessage( '文件大小超出限制 (2MB)。' );
                return false;
            }

            return true;
        };

        const onError = ( error ) => {
            setErrorMessage( error.message || '上传失败。' );
        };

        const onFileChange = ( event ) => {
            const files = event.target.files;

            if ( files && files.length > 0 ) {
                if ( validateFile( files ) ) {
                    // 手动触发 MediaUpload 的 onSelect,模拟文件上传成功
                    //  注意:这里需要手动创建一个包含文件信息的对象,传递给 onSelect
                    const reader = new FileReader();
                    reader.onload = (e) => {
                        const imageData = {
                            id: null, // 通常服务器会返回 ID,这里模拟
                            url: e.target.result, // 使用 Data URL
                            alt: file.name, // 使用文件名作为 Alt 文本
                            mime: file.type,
                            filename: file.name,
                        };
                        onSelectImage( imageData );
                    };
                    reader.readAsDataURL(file);
                }
            }
        };

        return (
            <div>
                { imageUrl ? (
                    <img src={ imageUrl } alt={ imageAlt } style={ { maxWidth: '100%' } } />
                ) : (
                    <div>
                        <input
                            type="file"
                            accept="image/*"
                            onChange={ onFileChange }
                        />
                        { errorMessage && <p style={ { color: 'red' } }>{ errorMessage }</p> }
                    </div>
                ) }
            </div>
        );
    },
    save: ( { attributes } ) => {
        const { imageUrl, imageAlt } = attributes;

        return imageUrl ? (
            <img src={ imageUrl } alt={ imageAlt } />
        ) : null;
    },
} );

这段代码的关键在于:

  • validateFile 函数: 该函数接收一个 FileList 对象作为参数,并对文件进行验证。在本例中,我们验证文件大小是否超过 2MB。如果验证失败,则调用 setErrorMessage 函数设置错误信息,并返回 false
  • onError 函数: 该函数接收一个 Error 对象作为参数,并在上传或选择文件发生错误时被调用。在本例中,我们调用 setErrorMessage 函数设置错误信息。
  • onFileChange 函数: 当用户通过 <input type="file"> 元素选择文件时触发。我们首先调用 validateFile 函数验证文件。如果验证通过,则手动模拟 MediaUploadonSelect 事件,将文件信息传递给 onSelectImage 函数。
  • 状态变量 errorMessage: 使用 useState hook 来存储和更新错误信息,并在界面上显示。

注意:

  1. 由于我们使用了 <input type="file">,因此不再直接使用 MediaUpload 组件。
  2. 我们需要手动创建一个包含文件信息的对象,并将其传递给 onSelectImage 函数,模拟 MediaUploadonSelect 事件。
  3. 为了获取文件内容,我们使用了 FileReader API,将文件读取为 Data URL。
  4. 在实际项目中,文件上传通常由服务器处理。在本例中,我们只是模拟了文件上传过程。如果需要将文件上传到服务器,可以使用 fetch API 或其他 AJAX 库。
  5. 此示例仅演示了文件大小验证。您可以根据需要添加其他验证逻辑,例如图片尺寸验证、文件类型验证等。

更高级的验证:图片尺寸验证

除了文件大小,我们还可以验证图片的尺寸。以下是一个验证图片尺寸的示例:

import { registerBlockType } from '@wordpress/blocks';
import { MediaUpload } from '@wordpress/block-editor';
import { useState } from '@wordpress/element';

registerBlockType( 'my-plugin/image-upload-block-with-size-validation', {
    title: '图片上传区块(带尺寸验证)',
    icon: 'format-image',
    category: 'common',
    attributes: {
        imageId: {
            type: 'number',
            default: null,
        },
        imageUrl: {
            type: 'string',
            default: '',
        },
        imageAlt: {
            type: 'string',
            default: '',
        },
    },
    edit: ( { attributes, setAttributes } ) => {
        const { imageId, imageUrl, imageAlt } = attributes;
        const [ errorMessage, setErrorMessage ] = useState( '' );

        const onSelectImage = ( media ) => {
            setAttributes( {
                imageId: media.id,
                imageUrl: media.url,
                imageAlt: media.alt,
            } );
            setErrorMessage( '' ); // 清除错误信息
        };

        const validateImageSize = ( file ) => {
            return new Promise((resolve, reject) => {
                const img = new Image();
                img.onload = () => {
                    const maxWidth = 800;
                    const maxHeight = 600;

                    if (img.width > maxWidth || img.height > maxHeight) {
                        reject(`图片尺寸超出限制 (最大 ${maxWidth}x${maxHeight} 像素)。`);
                    } else {
                        resolve();
                    }
                };
                img.onerror = () => {
                    reject('无法读取图片尺寸。');
                };
                img.src = URL.createObjectURL(file);
            });
        };

        const onFileChange = ( event ) => {
            const file = event.target.files[0];

            if (file) {
                validateImageSize(file)
                    .then(() => {
                        const reader = new FileReader();
                        reader.onload = (e) => {
                            const imageData = {
                                id: null, // 通常服务器会返回 ID,这里模拟
                                url: e.target.result, // 使用 Data URL
                                alt: file.name, // 使用文件名作为 Alt 文本
                                mime: file.type,
                                filename: file.name,
                            };
                            onSelectImage( imageData );
                        };
                        reader.readAsDataURL(file);
                    })
                    .catch(error => {
                        setErrorMessage(error);
                    });
            }
        };

        return (
            <div>
                { imageUrl ? (
                    <img src={ imageUrl } alt={ imageAlt } style={ { maxWidth: '100%' } } />
                ) : (
                    <div>
                        <input
                            type="file"
                            accept="image/*"
                            onChange={ onFileChange }
                        />
                        { errorMessage && <p style={ { color: 'red' } }>{ errorMessage }</p> }
                    </div>
                ) }
            </div>
        );
    },
    save: ( { attributes } ) => {
        const { imageUrl, imageAlt } = attributes;

        return imageUrl ? (
            <img src={ imageUrl } alt={ imageAlt } />
        ) : null;
    },
} );

这段代码的关键改进在于:

  • validateImageSize 函数: 该函数接收一个 File 对象作为参数,并使用 Image 对象来获取图片尺寸。由于 Image 对象的 onload 事件是异步的,因此我们使用了 Promise 来处理异步操作。如果图片尺寸超出限制,则 reject Promise,否则 resolve Promise。
  • onFileChange 函数: 该函数调用 validateImageSize 函数,并使用 thencatch 方法来处理 Promise 的结果。如果 Promise resolve,则继续读取文件并模拟 onSelect 事件;如果 Promise reject,则设置错误信息。

总结与未来方向

今天我们学习了如何利用 MediaUpload 组件处理媒体文件上传,并实现了自定义的文件验证。我们从 MediaUpload 组件的基本用法入手,逐步深入到自定义文件大小验证和图片尺寸验证的实现细节。通过这些示例,我们了解了如何使用 JavaScript 的 File API 和 Promise 来实现更复杂的验证逻辑。

几个要点:

  • MediaUpload 组件简化了媒体上传流程,但自定义验证需要额外处理。
  • File API 和 FileReader API 是实现前端验证的关键工具。
  • Promise 可以帮助我们处理异步验证操作。

未来方向:

  • 结合服务器端验证,构建更安全的媒体上传系统。
  • 实现更丰富的验证规则,例如文件类型验证、恶意代码检测等。
  • 优化用户体验,例如提供上传进度条、实时错误提示等。

希望今天的分享对大家有所帮助!

发表回复

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