React 实现文件分片上传和下载

简介: 在当今的前端开发中,处理文件流操作已经成为一个常见的需求。无论是上传、下载、读取、展示还是其他的文件处理操作,都需要高效且可靠地处理二进制数据。而随着文件大小的增加和网络传输的限制,文件分片上传和下载逐渐成为了提升性能和用户体验的必备技术。

📚 前言


在当今的前端开发中,处理文件流操作已经成为一个常见的需求。无论是上传、下载、读取、展示还是其他的文件处理操作,都需要高效且可靠地处理二进制数据。而随着文件大小的增加和网络传输的限制,文件分片上传和下载逐渐成为了提升性能和用户体验的必备技术。


文件分片上传和下载通过将大文件拆分成多个小片段,以及利用断点续传的特性,使得文件的传输更为可靠和高效。在这个过程中,前端开发者需要熟悉 Blob 对象和 ArrayBuffer,这些工具可以帮助我们处理和操控二进制数据。而使用 React 框架可以更加方便地管理和操作文件对象,并轻松实现文件的分片上传和下载功能。


本文将深入探讨如何使用 React 实现文件分片上传和下载,并介绍相关的基本概念和技术。我们将重点关注如何有效地处理文件的二进制数据,以及如何利用文件流操作优化前端开发中的文件处理任务。通过学习本文,您将能够掌握一套高效处理文件流操作的方法,为您的前端开发工作提供更好的解决方案。让我们一起开始探索吧!

 



文件传输是一个常见的需求。对于大文件的下载和上传,直接使用传统的方式可能会遇到性能和用户体验方面的问题。


幸运的是,前端技术提供了一些高效的解决方案:文件流操作和切片下载与上传。本文将深入探讨这些技术,帮助你理解它们的原理和实现方法,以优化文件传输效率和提升用户体验。


📘 一、前端文件流操作


在前端开发中,文件流操作是指通过数据流的方式处理文件,对文件进行读取、写入和展示等操作。下面详细介绍了前端文件流操作的几个基本概念和技术。


📟 数据流和文件处理的基本概念


数据流是指连续的数据序列,可以从一个源传输到另一个目的地。在前端开发中,文件可以被看作数据流的一种形式,可以通过数据流的方式进行处理。 文件处理涉及读取和写入文件的操作,包括读取文件的内容、写入数据到文件,以及对文件进行删除、重命名等操作。


📟 Blob 对象和 ArrayBuffer:处理二进制数据


在前端处理文件时,经常需要处理二进制数据。Blob(Binary Large Object)对象是用来表示二进制数据的一个接口,可以存储大量的二进制数据。Blob 对象可以通过构造函数进行创建,也可以通过其他 API 生成,例如通过 FormData 对象获取上传的文件。 而 ArrayBuffer 是 JavaScript 中的一个对象类型,用于表示一个通用的、固定长度的二进制数据缓冲区。我们可以通过 ArrayBuffer 来操作和处理文件的二进制数据。


📟 代码如下:


import React, { useState } from 'react';
function FileInput() {
  const [fileContent, setFileContent] = useState('');
  // 读取文件内容到ArrayBuffer
  function readFileToArrayBuffer(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      // 注册文件读取完成后的回调函数
      reader.onload = function(event) {
        const arrayBuffer = event.target.result;
        resolve(arrayBuffer);
      };
      // 读取文件内容到ArrayBuffer
      reader.readAsArrayBuffer(file);
    });
  }
  // 将ArrayBuffer转为十六进制字符串
  function arrayBufferToHexString(arrayBuffer) {
    const uint8Array = new Uint8Array(arrayBuffer);
    let hexString = '';
    for (let i = 0; i < uint8Array.length; i++) {
      const hex = uint8Array[i].toString(16).padStart(2, '0');
      hexString += hex;
    }
    return hexString;
  }
  // 处理文件选择事件
  function handleFileChange(event) {
    const file = event.target.files[0];  // 获取选中的文件
    if (file) {
      readFileToArrayBuffer(file)
        .then(arrayBuffer => {
          const hexString = arrayBufferToHexString(arrayBuffer);
          setFileContent(hexString);
        })
        .catch(error => {
          console.error('文件读取失败:', error);
        });
    } else {
      setFileContent('请选择一个文件');
    }
  }
  return (
    <div>
      <input type="file" onChange={handleFileChange} />
      <div>
        <h4>文件内容:</h4>
        <pre>{fileContent}</pre>
      </div>
    </div>
  );
}
export default FileInput;



上面代码里,我们创建了一个名为 FileInput 的函数式组件。该组件包含一个文件选择框和一个用于显示文件内容的 <pre> 元素。当用户选择文件时,通过 FileReader 将文件内容读取为 ArrayBuffer,然后将 ArrayBuffer 转换为十六进制字符串,并将结果显示在页面上。


📟 使用 FileReader 进行文件读取


FileReader 是前端浏览器提供的一个 API,用于读取文件内容。通过 FileReader,我们可以通过异步方式读取文件,并将文件内容转换为可用的数据形式,比如文本数据或二进制数据。 FileReader 提供了一些读取文件的方法,例如 readAsText()、readAsArrayBuffer() 等,可以根据需要选择合适的方法来读取文件内容。


📟 将文件流展示在前端页面中


一旦我们成功地读取了文件的内容,就可以将文件流展示在前端页面上。具体的展示方式取决于文件的类型。例如,对于文本文件,可以直接将其内容显示在页面的文本框或区域中;对于图片文件,可以使用 <img> 标签展示图片;对于音视频文件,可以使用 <video> 或 <audio> 标签来播放。 通过将文件流展示在前端页面上,我们可以实现在线预览和查看文件内容的功能。


好的,这一部分就基本介绍完毕,总结一下。前端文件操作流是处理大型文件的一种常见方式,他可以通过数据流的方式对文件进行操作。Blob对象 和 ArrayBuffer是处理二进制数据的重要工具。而FileReader则是读取文件内容的的关键组件。通过这些技术,我们可以方便的在前端页面上进行操作或者文件展示。


📘 二、文件切片下载


这一步就进入到我们今天文章主题了,先来主要的看下流程


graph LR
A(开始) --> B{选择文件}
B -- 用户选择文件 --> C[切割文件为多个切片]
C --> D{上传切片}
D -- 上传完成 --> E[合并切片为完整文件]
E -- 文件合并完成 --> F(上传成功)
D -- 上传中断 --> G{保存上传进度}
G -- 上传恢复 --> D
G -- 取消上传 --> H(上传取消)


📟 传统文件下载的性能问题


文件切片下载是一种提升文件下载效率的技术,通过将大文件分割成多个小片段(切片),并使用多个并发请求同时下载这些切片,从而加快整体下载速度。


传统的文件下载方式对于大文件来说存在性能问题。当用户请求下载一个大文件时,服务器需要将整个文件发送给客户端。这会导致以下几个问题:


较长的等待时间:大文件需要较长的时间来传输到客户端,用户需要等待很长时间才能开始使用文件。


网络阻塞:由于下载过程中占用了网络带宽,其他用户可能会遇到下载速度慢的问题。


断点续传困难:如果下载过程中出现网络故障或者用户中断下载,需要重新下载整个文件,无法继续之前的下载进度。


📟 利用文件切片提升下载效率




文件切片下载通过将文件分割成多个小片段,每个片段大小通常在几百KB到几MB之间。然后客户端通过多个并发请求同时下载这些片段。这样做的好处是:


快速启动:客户端可以快速开始下载,因为只需要下载第一个切片即可。


并发下载:通过使用多个并发请求下载切片,可以充分利用带宽,并提高整体下载速度。


断点续传:如果下载中断,客户端只需要重新下载中断的切片,而不需要重新下载整个文件。


切片上传代码示例:


const [selectedFile, setSelectedFile] = useState(null); 
const [progress, setProgress] = useState(0);
 // 处理文件选择事件
 function handleFileChange(event) {
   setSelectedFile(event.target.files[0]);
 }
 // 处理文件上传事件
 function handleFileUpload() {
   if (selectedFile) {
     // 计算切片数量和每个切片的大小
     const fileSize = selectedFile.size;
     const chunkSize = 1024 * 1024; // 设置切片大小为1MB
     const totalChunks = Math.ceil(fileSize / chunkSize);
     // 创建FormData对象,并添加文件信息
     const formData = new FormData();
     formData.append('file', selectedFile);
     formData.append('totalChunks', totalChunks);
     // 循环上传切片
     for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) {
       const start = chunkNumber * chunkSize;
       const end = Math.min(start + chunkSize, fileSize);
       const chunk = selectedFile.slice(start, end);
       formData.append(`chunk-${chunkNumber}`, chunk, selectedFile.name);
     }
     // 发起文件上传请求
     axios.post('/upload', formData, {
       onUploadProgress: progressEvent => {
         const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
         setProgress(progress);
       }
     })
       .then(response => {
         console.log('文件上传成功:', response.data);
       })
       .catch(error => {
         console.error('文件上传失败:', error);
       });
   }
 }



当涉及到切片上传和下载时,前端使用的技术通常是基于前端库或框架提供的文件处理功能,结合后端服务实现。


上面代码里我们提到了文件如何切片上传。


当用户选择文件后,通过 handleFileChange 函数处理文件选择事件,将选择的文件保存在 selectedFile 状态中。


当用户点击上传按钮时,通过 handleFileUpload 函数处理文件上传事件。


在 handleFileUpload 函数中,计算切片数量和每个切片的大小,并创建一个 FormData 对象用于存储文件信息和切片数据。


📟 实现客户端切片下载的方案


实现客户端切片下载的基本方案如下:


服务器端将大文件切割成多个切片,并为每个切片生成唯一的标识符。


客户端发送请求获取切片列表,同时开始下载第一个切片。


客户端在下载过程中,根据切片列表发起并发请求下载其他切片,并逐渐拼接合并下载的数据。


当所有切片都下载完成后,客户端将下载的数据合并为完整的文件。


代码示例:


function downloadFile() {
  // 发起文件下载请求
  fetch('/download', {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
    },
  })
    .then(response => response.json())
    .then(data => {
      const totalSize = data.totalSize;
      const totalChunks = data.totalChunks;
      let downloadedChunks = 0;
      let chunks = [];
      // 下载每个切片
      for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) {
        fetch(`/download/${chunkNumber}`, {
          method: 'GET',
        })
          .then(response => response.blob())
          .then(chunk => {
            downloadedChunks++;
            chunks.push(chunk);
            // 当所有切片都下载完成时
            if (downloadedChunks === totalChunks) {
              // 合并切片
              const mergedBlob = new Blob(chunks);
              // 创建对象 URL,生成下载链接
              const downloadUrl = window.URL.createObjectURL(mergedBlob);
              // 创建 <a> 元素并设置属性
              const link = document.createElement('a');
              link.href = downloadUrl;
              link.setAttribute('download', 'file.txt');
              // 模拟点击下载
              link.click();
              // 释放资源
              window.URL.revokeObjectURL(downloadUrl);
            }
          });
      }
    })
    .catch(error => {
      console.error('文件下载失败:', error);
    });
}


我们看下代码,首先使用BLOB对象创建一共对象URL,用于生成下载连接,然后创建a标签并且设置href的属性为刚刚创建的对象URL,继续设置a标签的download属性是文件名,方便点击的时候自动下载文件。


📟 显示下载进度和完成状态


为了显示下载进度和完成状态,可以在客户端实现以下功能:


显示进度条:客户端可以通过监听每个切片的下载进度来计算整体下载进度,并实时更新进度条的显示。


显示完成状态:当所有切片都下载完成后,客户端可以显示下载完成的状态,例如显示一个完成的图标或者文本。


这里我们可以继续接着切片上传代码示例里的继续写。


代码示例:


// 处理文件下载事件
  function handleFileDownload() {
    axios.get('/download', {
      responseType: 'blob',
      onDownloadProgress: progressEvent => {
        const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
        setProgress(progress);
      }
    })
      .then(response => {
        // 创建一个临时的URL对象用于下载
        const url = window.URL.createObjectURL(new Blob([response.data]));
        const link = document.createElement('a');
        link.href = url;
        link.setAttribute('download', 'file.txt');
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      })
      .catch(error => {
        console.error('文件下载失败:', error);
      });
  }
  <button onClick={handleFileDownload}>下载文件</button>
  <div>进度:{progress}%</div>


 

当用户点击下载按钮时,通过 handleFileDownload 函数处理文件下载事件。


在 handleFileDownload 函数中,使用 axios 库发起文件下载请求,并设置 responseType: 'blob' 表示返回二进制数据。


通过监听 onDownloadProgress 属性获取下载进度,并更新进度条的显示。


下载完成后,创建一个临时的 URL 对象用于下载,并通过动态创建 <a> 元素模拟点击下载。


📘 三、大文件上传的问题与解决方案


📟 传统的文件上传方式存在的问题


大文件上传耗时长,容易导致请求超时。


占用服务器和网络带宽资源,可能影响其他用户的访问速度。


如果上传中断,需要重新上传整个文件,效率低下。


难以实现上传进度的显示和控制。


📟 前端文件切片上传的优势


将大文件分割为更小的文件切片,分多次上传,提高上传效率和稳定性。


提供上传进度的监控和展示,提高用户体验。


充分利用浏览器的并发上传能力,减轻服务器负担。


实现断点续传功能,避免重复上传已上传的部分。


📟 实现前端切片上传的方法


- 使用 JavaScript 的 `File API` 获取文件对象,并使用 `Blob.prototype.slice()` 方法将文件切割为多个切片。

使用 FormData 对象将切片数据通过 AJAX 或 Fetch API 发送到服务器。


在后端服务器上接收切片并保存到临时存储中,等待后续合并。


在客户端通过监听上传进度事件,在进度条或提示中展示上传进度。


📟 代码示例


const [file, setFile] = useState(null);  //用来存放我本地上传的文件
const chunkSize = 1024 * 1024; // 1MB 切片大小
  const upload = () => {
    if (!file) {
      alert("请选择要上传的文件!");
      return;
    }
    const chunkSize = 1024 * 1024; // 1MB
    let start = 0;
    let end = Math.min(chunkSize, file.size);
    while (start < file.size) {
      const chunk = file.slice(start, end);
     
      // 创建FormData对象
      const formData = new FormData();
      formData.append('file', chunk);
      // 发送切片到服务器
      fetch('上传接口xxxx', {
        method: 'POST',
        body: formData
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        // 处理响应结果
      })
      .catch(error => {
        console.error(error);
        // 处理错误
      });
      start = end;
      end = Math.min(start + chunkSize, file.size);
    }
  };
 
 return (
    <div>
      <input type="file" onChange={handleFileChange} />
      <button onClick={upload}>上传</button>
    </div>
  );
}



在上面的代码中,创建了一个名为Upload的函数组件。它使用了 React 的useState钩子来管理选中的文件。


通过onChange事件监听文件输入框的变化,并在handleFileChange函数中获取选择的文件,并更新file状态。


点击“上传”按钮时,调用upload函数。它与之前的示例代码类似,将文件切割为多个大小相等的切片,并使用FormData对象和fetch函数发送切片数据到服务器。


📟 实现断点续传的技术:记录和恢复上传状态


在前端,可以使用 localStorage 或 sessionStorage 来存储已上传的切片信息,包括已上传的切片索引、切片大小等。


每次上传前,先检查本地存储中是否存在已上传的切片信息,若存在,则从断点处继续上传。


在后端,可以使用一个临时文件夹或数据库来记录已接收到的切片信息,包括已上传的切片索引、切片大小等。


在上传完成前,保存上传状态,以便在上传中断后能够恢复上传进度。


import React, { useState, useRef, useEffect } from 'react';
function Upload() {
  const [file, setFile] = useState(null);
  const [uploadedChunks, setUploadedChunks] = useState([]);
  const [uploading, setUploading] = useState(false);
  const uploadRequestRef = useRef();
  const handleFileChange = (event) => {
    const selectedFile = event.target.files[0];
    setFile(selectedFile);
  };
  const uploadChunk = (chunk) => {
    // 创建FormData对象
    const formData = new FormData();
    formData.append('file', chunk);
    // 发送切片到服务器
    return fetch('your-upload-url', {
      method: 'POST',
      body: formData
    })
    .then(response => response.json())
    .then(data => {
      console.log(data);
      // 处理响应结果
      return data;
    });
  };
  const upload = async () => {
    if (!file) {
      alert("请选择要上传的文件!");
      return;
    }
    const chunkSize = 1024 * 1024; // 1MB
    const totalChunks = Math.ceil(file.size / chunkSize);
    let start = 0;
    let end = Math.min(chunkSize, file.size);
    setUploading(true);
    for (let i = 0; i < totalChunks; i++) {
      const chunk = file.slice(start, end);
      const uploadedChunkIndex = uploadedChunks.indexOf(i);
      if (uploadedChunkIndex === -1) {
        try {
          const response = await uploadChunk(chunk);
          setUploadedChunks((prevChunks) => [...prevChunks, i]);
          // 保存已上传的切片信息到本地存储
          localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks));
        } catch (error) {
          console.error(error);
          // 处理错误
        }
      }
      start = end;
      end = Math.min(start + chunkSize, file.size);
    }
    setUploading(false);
    // 上传完毕,清除本地存储的切片信息
    localStorage.removeItem('uploadedChunks');
  };
  useEffect(() => {
    const storedUploadedChunks = localStorage.getItem('uploadedChunks');
    if (storedUploadedChunks) {
      setUploadedChunks(JSON.parse(storedUploadedChunks));
    }
  }, []);
  return (
    <div>
      <input type="file" onChange={handleFileChange} />
      <button onClick={upload} disabled={uploading}>
        {uploading ? '上传中...' : '上传'}
      </button>
    </div>
  );
}


首先,使用useState钩子创建了一个uploadedChunks状态来保存已上传的切片索引数组。初始值为空数组。


然后,我们使用useRef钩子创建了一个uploadRequestRef引用,用于存储当前的上传请求。


在handleFileChange函数中,我们更新了file状态以选择要上传的文件。


在uploadChunk函数中,我们发送切片到服务器,并返回一个Promise对象来处理响应结果。


在upload函数中,我们添加了断点续传的逻辑。首先,我们获取切片的总数,并设置uploading状态为true来禁用上传按钮。


然后,我们使用for循环遍历所有切片。对于每个切片,我们检查uploadedChunks数组中是否已经包含该索引,如果不包含,则进行上传操作。


在上传切片之后,我们将已上传的切片索引添加到uploadedChunks数组,并使用localStorage保存已上传的切片信息。


最后,在上传完毕后,我们将uploading状态设为false,并清除本地存储的切片信息。


在实现大文件上传时要考虑服务器端的处理能力和存储空间,以及安全性问题。同时,为了保障断点续传的准确性,应该尽量避免并发上传相同文件的情况,可以采用文件唯一标识符或用户会话标识符进行区分。


📘 四、优化用户体验:切片下载与上传的应用场景


📟 后台管理系统中的文件下载和上传:


文件下载:在后台管理系统中,用户可能需要下载大型文件,如报表、日志文件、数据库备份等。通过将文件切片下载,可以提高下载速度和稳定性,同时允许用户中断下载并从中断处继续下载。


文件上传:后台管理系统中,用户可能需要上传大型文件,如数据导入、文件备份等。使用切片上传可以提高上传效率,分批上传文件切片,并显示上传进度,使用户能够了解上传的状态。


📟 图片/视频上传和预览:


图片上传和预览:在图片上传场景中,用户可以选择多张图片进行上传。通过切片上传,可以加快图片上传速度,并实时显示上传进度。同时,在上传完成后,可以提供预览功能,让用户可以立即查看上传的图片。


视频上传和预览:对于较大的视频文件,切片上传可以确保上传过程可靠且高效。同时,可以实现上传进度的实时展示。上传完成后,通过切片下载技术,用户可以流畅地观看视频,无需等待整个文件下载完成。


📟 云存储和云盘应用中的文件操作:


文件分块上传:云存储和云盘应用通常需要处理大量文件的上传。通过切片上传可以提高上传速度和稳定性,并允许用户中断并继续上传。


文件分块下载:当用户需要下载云存储或云盘中的大型文件时,可以使用切片下载技术,加快下载速度并提供中断恢复功能。


文件预览和在线编辑:通过将文件切片并进行预览,在线编辑,可以提供更好的用户体验。用户可以在不需完全下载文件的情况下,直接预览和编辑文件。


📚 写在最后


文件分片上传和下载是一种优化前端文件流操作的有效方案。通过将大文件拆分成多个小片段,并利用断点续传的特性,我们可以提升文件传输的可靠性和效率。


在这个过程中,使用 React 框架可以方便地管理和操作文件对象,提供了更便捷的编程方式。同时,熟悉 Blob 对象和 ArrayBuffer 这些工具也是处理和操作二进制数据的关键。


通过本文的学习,我们深入了解了文件流操作的基本概念和技术,以及如何使用 React 实现文件分片上传和下载。我们掌握了处理二进制数据的方法,并了解了如何利用流式传输和断点续传的技术优化文件传输的性能和用户体验。


文件分片上传和下载的实现,不仅为前端开发提供了高效的文件处理方式,还能够应对网络限制和大文件传输的挑战。掌握这些知识和技术,将为我们在前端开发中处理文件流操作提供一个强大的工具箱。


总之,通过合理运用文件分片上传和下载以及相关的前端技术,我们可以优化文件传输的性能,提升用户体验,并为前端开发带来更好的解决方案。

相关文章
|
4月前
|
JSON JavaScript 前端开发
前端 ex2json 用于 vue/react/js 将 xls、xlsx、csv 文件转成 json 数组
前端 ex2json 用于 vue/react/js 将 xls、xlsx、csv 文件转成 json 数组
93 0
|
4月前
|
前端开发 JavaScript
前端知识笔记(二十六)———React如何像Vue一样将css和js写在同一文件
前端知识笔记(二十六)———React如何像Vue一样将css和js写在同一文件
30 1
|
8月前
|
SQL 缓存 前端开发
React 实现文件分片上传和下载
在开发中,文件的上传和下载是常见的需求。然而,当面对大型文件时,直接的上传和下载方式可能会遇到一些问题,比如网络传输不稳定、文件过大导致传输时间过长等等。为了解决这些问题,我们可以使用文件分片上传和下载的方式来提高效率和稳定性。 文件分片上传的主要思想是将大文件分解成多个较小的部分,然后分别进行上传,最后在服务端将这些小部分合并成完整的文件。同样,文件分片下载也是将大文件分解成多个分片,然后分别下载,最后在客户端将这些分片合并成完整的文件。
256 1
|
8月前
|
前端开发 API 开发工具
阿里云oss开发实践:大文件分片、断点续传、实时进度 React+Node+Socket.IO
阿里云oss开发实践:大文件分片、断点续传、实时进度 React+Node+Socket.IO
826 1
|
10月前
使用 react.lazy 打包之后得文件如何不显示数字 #67
使用 react.lazy 打包之后得文件如何不显示数字 #67
39 0
|
前端开发
#yyds干货盘点 【React工作记录二十】前端常规关于网页文件下载的问题
#yyds干货盘点 【React工作记录二十】前端常规关于网页文件下载的问题
74 0
|
前端开发
#yyds干货盘点 react笔记之学习之使用create-react-app创建文件
#yyds干货盘点 react笔记之学习之使用create-react-app创建文件
82 0
|
前端开发
react笔记之学习之使用create-react-app创建文件
react笔记之学习之使用create-react-app创建文件
67 0
react笔记之学习之使用create-react-app创建文件
|
JavaScript 前端开发
react 在js文件中实现和jsx页面的双向绑定
react 在js文件中实现和jsx页面的双向绑定
|
数据采集 缓存 资源调度
React脚手架及脚手架文件详细介绍
React脚手架及脚手架文件详细介绍
291 0
React脚手架及脚手架文件详细介绍