上传文件的异步验证(React Dropzone + TS)

问题描述 投票:0回答:2

我需要使用 React Dropzone 验证上传视频的持续时间。问题是我发现的每个方法都是异步的,而库的验证器函数应该是同步的。如何在同步函数中运行异步代码?

获取视频时长功能:

async function getVideoDuration(videoFile: File | Blob) {
  return new Promise<number>((res, _) => {
    const video = document.createElement("video")
    video.preload = "metadata"
    video.onloadeddata = function () {
      res(video.duration)
    }
    video.src = URL.createObjectURL(videoFile)
  })
}

我的 React Zone 验证器功能:

    validator(f) {
      let file: (File) | null
      if (f instanceof DataTransferItem) {
        file = f.getAsFile()
      } else file = f
      if (!file) return { message: "File Expected", code: "IvalidDragObject" }
      console.log(file)

      if (file.size > 5e8) return { message: "File too large (max 500 MB)", code: "FileTooLarge" }

      if (!file) return { message: "File Expected", code: "IvalidDragObject" }
const duration = await getVideoDuration(file) // 'await' expressions are only allowed within async functions and at the top levels of modules.

      if (duration > 15 * 60) {
        return { message: "File Duration should be less than 15 minutes", code: "LargeFileDuration" }
      }

      return null
    },

其他解决方法是使用

getFilesFromEvent
函数并将自定义道具传递到那里的文件,但它给出的事件非常不同,并且实现起来很乏味。

javascript reactjs typescript es6-promise react-dropzone
2个回答
3
投票

我已经在here分叉了这个项目,并进行了支持异步验证所需的更改。请注意,目前不支持

noDragEventsBubbling
参数。只需传入一个异步(或同步)验证回调函数,如下所示,并按最初的预期使用。

import { useDropzone } from 'react-dropzone'

function MyDropzone() {
  const onDrop = useCallback(acceptedFiles => {
    // Do something with the files
  }, [])
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDrop,
    validator: async (file) => {
      // do validation here
    }
  })

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      {
        isDragActive ?
          <p>Drop the files here ...</p> :
          <p>Drag 'n' drop some files here, or click to select files</p>
      }
    </div>
  )
}

0
投票

发现这种方法是最模块化的并且接近react-dropzone的原始设计。没有任何额外的依赖。在我们的例子中,我们检查图像的大小,但您可以看到添加其他 MIME 类型的验证不会有问题。

文件 getter.ts

import {DropEvent} from 'react-dropzone'

interface ExtendedFile extends File {
    width?: number
    height?: number
}

const extractFiles = async (e: DropEvent) => {
    const files: File[] = []
    if (e instanceof Array) {
        const fileHandles = e.filter((it) => it instanceof FileSystemFileHandle)
        const selectedFiles = await Promise.all(fileHandles.map((it) => it.getFile()))
        files.push(...selectedFiles)
    } else if (e instanceof DragEvent) {
        files.push(...(e.dataTransfer?.files || []))
    } else if (e.target instanceof HTMLInputElement) {
        files.push(...Array.from(e.target.files || []))
    }
    return files
}

type ResolveType = (value: ExtendedFile | PromiseLike<ExtendedFile>) => void
const extractDimensionForImage = (file: File, resolve: ResolveType): void => {
    const image = new Image()
    image.onload = () => {
        Object.defineProperties(file, {
            width: {value: image.width},
            height: {value: image.height},
        })
        resolve(file satisfies ExtendedFile)
    }
    image.src = URL.createObjectURL(file)
}

const convertToExtended = async (files: File[]): Promise<ExtendedFile[]> => {
    const promises = files.map(
        (file) =>
            new Promise<ExtendedFile>((resolve) => {
                if (file.type.includes('image/')) return extractDimensionForImage(file, resolve)
                resolve(file)
            }),
    )
    return Promise.all(promises)
}

export const fileGetter = async (e: DropEvent) => {
    const files = await extractFiles(e)
    return convertToExtended(files)
}

dropzone.tsx

useDropzone({
    getFilesFromEvent: fileGetter,
    ...dropzoneOptions,
})

使用方法

const validateImage = (file: ExtendedFile): ReturnType<Required<DropzoneOptions>['validator']> => {
    const {width, height} = file
    if (!width || !height) return null
    if (width !== height) {
        return {
            code: 'invalid-size',
            message: 'Uploaded image must be squared',
        }
    }
    return null
}


const dropzoneOptions = {
    accept: {
        'image/svg+xml': ['.svg'],
    },
    validator: validateImage
}
© www.soinside.com 2019 - 2024. All rights reserved.