前景

在我们日常的工作中,文件上传是一个很常见的功能,文件上传说简单也简单,说它复杂也可以很复杂,比如说我们实现一个原生的上传组件我们只需设置一个<input type='file' /> ,通过inputonchange事件就能拿到我们文件数据然后通过FormData传到后端就完成了一个简单的上传功能,如果要严谨一点可以在文件上传时我们可以对它做一些文件大小、格式等校验,或者我们也可以通过给它添加拖拽上传进度条显示等,再复杂的话涉及到大文件上传的话,我们可以切片上传,网络不好可以通过断点续传,为了防止上传时出现阻塞主进程的现象我们可以利用React 中fiber架构的思想,利用浏览器空闲的时间计算并上传文件等等…

下面我会通过这几个案例逐步深入去了解在我们前端里是怎样一步步实现文件上传的 {% note info green %} 整个项目是基于Umijs+React搭建的,后台是用Eggjs+mongoose {% endnote %}

文件拖拽+上传+进度条

我们的大概需求是这样的,用户可以点击按钮上传按钮选择文件也可以通过拖拽的方式来上传文件,然后我们底部会有一个进度条显示上传的一个进度 uploadPage

上传页面

首先我们用一个div简单包裹我们input上传组件,我们给div添加拖拽等事件,当用户拖拽文件到div容器里时,我们给边框加个红色,离开或者鼠标松开时,边框变成正常的颜色,这样我们就有一个简单的动态效果,然后我们既可以通过拖拽事件来上传文件,也可以通过点击上传按钮选择上传

   <div>
        <div style={{ borderColor }} draggable onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} className={styles.drag}>
            <input type="file" onChange={handleChangeFile} />
        </div>
        {
            file !== null && <div>
                文件名<span>{file.name}</span>
            </div>
        }
        <Progress percent={progressNum} />
        <Button type='primary' disabled={file == null} onClick={handleUpload}>上传</Button>
    </div>

自定义监听

{% note info green %} 监听拖拽的事件,因为我们用的是函数组件,这里也会使用函数组件的方式去写我们的监听事件 {% endnote %}

// 我们自定义的一个hook监听
import useEventListener from '../hooks/useEventListener';
export default function UploadPage (){
        // 鼠标拖拽文件移入事件
    const handleDragOver = (e) => {
        e.preventDefault()
        setBorderColor('red')
    }
    // 离开事件
    const handleDragLeave = (e) => {
        e.preventDefault()
        setBorderColor('#eee')
    }
    // 文件放下事件
    const handleDrop = (e) => {
        const fileList = e.dataTransfer.files;
        setFile(fileList[0]);
        setBorderColor('#eee')
        e.preventDefault()
      
    }
    useEventListener("dragover", handleDragOver);
    useEventListener("dragleave", handleDragLeave)
    useEventListener("drop", handleDrop)
}

{% note info green %} 自定义hook监听函数 {% endnote %}

import { useRef , useEffect } from 'react';
export default function useEventListener(eventName, handler, element = window) {
	const savedHandler = useRef();
	useEffect(
		() => {
			savedHandler.current = handler;
		},
		[ handler ]
	);
	useEffect(
		() => {
			const isSupported = element && element.addEventListener;
			if (!isSupported) return;
			const eventListener = (event) => savedHandler.current(event);
			element.addEventListener(eventName, eventListener);
			return () => {
				element.removeEventListener(eventName, eventListener);
			};
		},
		[ eventName, element ]
	);
}

上传进度条

我们这里用的是antd 中的 Progress 组件,关于上传进度计算,我们这里用到的是 axios 中的post请求中有一个自带的方法可以计算我们上传文件的进度,叫做onUploadProgress,我们可以直接拿来用

    // 上传
   const handleUpload = () => {
        if (!file) {
            message.error('请选择文件')
        }
        const formData = new FormData();
        formData.append('file', file);
        formData.append('filename', file.name)
        // 上传
        instance.post(UPLOAD_FILE, formData,{
            // axios自带的上传方法,我们这里可以直接拿过来使用
            onUploadProgress:progress => {
                const count = Number(((progress.loaded/progress.total)*100).toFixed(2));
                setProgressNum(count)
                console.log(count,'count..')
            }
        }).then(res => {
            console.log(res, '文件上传')
        })
    }

完整代码

import React, { useEffect, useRef, useState } from 'react';
import { Button, message , Progress  } from 'antd';

import instance from '@/api/instance';
import { UPLOAD_FILE, INFO } from '@/api/api';
import styles from './home.less';
import useEventListener from '../hooks/useEventListener';

export default function Tuozhuai() {
    const [file, setFile] = useState(null);
    const [progressNum,setProgressNum] = useState(0)
    const [borderColor, setBorderColor] = useState('#eee')
    const handleChangeFile = (e) => {
        const file = e.target.files[0];
        setFile(file)
        console.log(e.target.files, 'files.....')
    }
    const handleUpload = () => {
        if (!file) {
            message.error('请选择文件')
        }
        const formData = new FormData();
        formData.append('file', file);
        formData.append('filename', file.name)
        // 上传
        instance.post(UPLOAD_FILE, formData,{
            // axios自带的上传方法,我们这里可以直接拿过来使用
            onUploadProgress:progress => {
                const count = Number(((progress.loaded/progress.total)*100).toFixed(2));
                setProgressNum(count)
                console.log(count,'count..')
            }
        }).then(res => {
            console.log(res, '文件上传')
        })
    }
    // 鼠标拖拽文件移入事件
    const handleDragOver = (e) => {
        e.preventDefault()
        setBorderColor('red')
    }
    // 离开事件
    const handleDragLeave = (e) => {
        e.preventDefault()
        setBorderColor('#eee')
    }
    // 文件放下事件
    const handleDrop = (e) => {
        const fileList = e.dataTransfer.files;
        setFile(fileList[0]);
        setBorderColor('#eee')
        e.preventDefault()
      
    }
    useEventListener("dragover", handleDragOver);
    useEventListener("dragleave", handleDragLeave)
    useEventListener("drop", handleDrop)
    return (
        <div>
            <div style={{ borderColor }} draggable onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} className={styles.drag}>
                <input type="file" onChange={handleChangeFile} />
            </div>
            {
                file !== null && <div>
                    文件名<span>{file.name}</span>
                </div>
            }
            <Progress percent={progressNum} />
            <Button type='primary' disabled={file == null} onClick={handleUpload}>上传</Button>
        </div>
    )
}