文件上传的前端核心认知与实践指南

250 阅读4分钟

文件上传基本使用认知

前端上传文件传给后端两种形式:二进制blob base64

相关对象

  • files:通过<input type="file"/ >读取的文件对象,属于blob(不可改)
  • blob: 不可变的二进制内容,包含很多操作方法
  • formData:前端通过input选择的files是属于前端的对象,后端是拿不到的,这时候formData就充当前后端都认识的载体来传递文件
  • fileReader:把文件都取成某种形式,如base64,text文本
  • 我们的文件上传和断点上传都是基于blob的
  • 二进制blob传输是formData承载着files对象进行的传输

files

首先我们来感受一下files是什么,我们通过input上传:

  <script setup>
  // 在这里编写组件逻辑
  import { ref } from 'vue'const fileChange = (e) => {
    console.log(e.target.files)
  }
  </script><template>
    <div>
      <input type="file" @change="fileChange" />
    </div>
  </template>

便可以直接看到files的内容结构:这里我上传的是图片类型:

6ac346eeaed0fadcd5740a98843eff74.png 由于可能是上传多文件,这里我们拿到的是一个数组,具体的files[0]files[1]才是文件首要对象,在真实场景中肯定会做对用户文件上传类型和大小的限制,可以从files[0].type入手:

  
  <script setup>
  import { ref } from 'vue'const fileChange = (e) => {
    const file = e.target.files[0] // 获取单个文件对象
    if (!file) return // 处理无文件上传的情况// 检查文件大小(10MB限制)
    if (file.size > 10 * 1024 * 1024) {
      alert('文件大小不能超过10MB')
      e.target.value = '' // 清空文件选择
      return
    }
  ​
    // 检查文件类型
    if (!['image/jpeg', 'image/png', 'video/mp4'].includes(file.type)) {
      alert('文件类型必须是JPEG、PNG或MP4')
      e.target.value = '' // 清空文件选择
      return
    }
  ​
    // 处理MP4文件的特殊逻辑
    if (file.type === 'video/mp4') {
      console.log('上传了MP4文件:', file)
    } else {
      console.log('上传了图片:', file)
    }
  }
  </script><template>
    <div>
      <input type="file" @change="fileChange" />
    </div>
  </template>

这样就很好地限制了文件的类型、大小等

可以自己new一个对象来创建file文件:

  
  new File('[Jice19]','a.txt')   //创建了一个a.txt文本文件,第一个对象是数组

file与blob的联系

刚才我们了解到file文件属于blob,那么自然能够实现相互转换,

  1. 实现了文件转换成blob
  
  console.log(new Blob([file])) //第一个对象必须是数组

1751888651403.png

2.实现了blob转换成files:需要了解blob可以把文件进行切片,这里我们做简单实现,后面详细讲解

  let sliceBlob = new Blob([file]).slice(0,100)   //切掉file的前半部分创建新对象
  let sliceFile = new File([sliceBlob])  //将切割后的file对象上传上去

fileReader

ddbf501a-d1bd-4e77-8133-0958a64b7036_1751894323513647018~tplv-a9rns2rl98-web-thumb-wm-avif.webp

作用将blob和file对象转换成Base64或文字格式

注意由于读取是异步的,我们不能直接拿到结果,我们需要监听读取的onload事件获取到转化结果

  //文件读取示例
  let sliceBlob = new Blob([file]).slice(0,100)   //切掉file的前半部分创建新对象
  let sliceFile = new File([sliceBlob])  //将切割后的file对象上传上去
  let fr = new FileReader()
  fr.readAsDataURL(sliceFile)  //将切片后的文件转换成base64
      fr.onload=function(){
          console.log(fr.result)     //只有在onload监听时才能拿到结果    
      }

通过读取同一张文件的截取之前之后(file /sliceFile)可以很明显地看出切片效果:

c5218efc53f860efcff229f91811e1ee.png

实战:缩略图的实现、用户上传文件文本预览

我们想要预览用户上传的图片文件,只需要动态绑定上通过Filereader读取的、只在onload这一事件时得到的文件,即可实现预览:

  
  <script setup>
  import { ref } from 'vue'
  ​
  const imgBase64 = ref('')
  const isLoading = ref(false)
  const errorMsg = ref('')
  ​
  const fileChange = (e) => {
    const file = e.target.files[0]
    if (!file) return
    
    // 检查文件类型是否为图片
    if (!file.type.startsWith('image/')) {
      errorMsg.value = '请上传图片文件'
      return
    }
  ​
    isLoading.value = true
    errorMsg.value = ''
    
    const reader = new FileReader()
    
    reader.onload = (event) => {
      imgBase64.value = event.target.result
      isLoading.value = false
    }
    
    reader.onerror = () => {
      errorMsg.value = '图片读取失败'
      isLoading.value = false
    }
    //读取到文件转换成base64然后绑定给img实现用户文件预览
    reader.readAsDataURL(file)
  }
  </script>
  ​
  <template>
    <div>
      <input type="file" @change="fileChange" />
      
      <div v-if="errorMsg">{{ errorMsg }}</div>
      
      <div v-else-if="isLoading">
        图片加载中...
      </div>
      
      <div v-else-if="imgBase64">
        <img style='height:200px;width:200px' :src="imgBase64" alt="用户上传的图片" />
      </div>
    </div>
  </template>

(注:文本文件也是同理,通过reader.readAsText实现预览)

66b956e701ccc05e0bf1a046276a85fc.png

文件传送给后端

我们刚才了解到通过formData将数据传递给后端,先new一个formData对象,我们读取到文件之后,直接把文件给到对象,然后通过formData对象的append方法搭载数据,最后直接把对象传递给接口就好

0e1f88e6-4223-42a5-b432-9a01d955995a_1751894322954744746~tplv-a9rns2rl98-web-thumb-wm-avif.webp

  
  //实现思路
  <template>
      <form method="post">
          <input type="text" @change="fileChange" />
      </form>
      <button @click="submit">提交</button>
  </template><script setup>async submit(){
      let formData = new FormData()
      formData.append('file1',file1)
      formData.append('file2',file2)
      axios.post('/xx',formData)
  }
    
  </script>

相关对象的转换关系

4e578d77b47dd90f024b0f0ce3d8351b.png