vc-upload源码分析 -- ant-design-vue系列

news2024/9/25 1:20:14

vc-upload源码分析 – ant-design-vue系列

1 整体结构

上传组件的使用分两种:点击上传和拖拽上传。

点击的是组件或者是卡片,这个是用户通过插槽传递的。除上传外的其他功能,比如预览、自定义文件渲染等功能,也不是上传的核心功能。

上传是通过vc-upload组件来实现的。整体结构如下:

在这里插入图片描述

2 源码分析

vc-upload中,Upload.tsx的逻辑比较少,包括:设置componentTag: 'span',给<AjaxUpload>对应的节点挂上abort方法等等。主要逻辑在AjaxUpload.tsx组件中。

2.1 渲染函数(重点)

先看一下最后的渲染函数。

🎯 浏览器调用文件选择,常用的只有 <input type="file" />这种方法。

  • 为了自定义样式,所以input组件是不可见的,当我们点击Tag区域时,需要触发input的点击事件,这个可以通过input的引用:fileInput.value.click()来实现。

这里的Tag,不是组件,而是Upload.tsx组件中设置的默认值span

<Tag {...events} class={cls} role="button" style={attrs.style}>
  <input
    {...pickAttrs(otherProps, { aria: true, data: true })}
    id={id}
    type="file"
    ref={fileInput}
    onClick={e => e.stopPropagation()} // https://github.com/ant-design/ant-design/issues/19948
    key={uid.value}
    style={{ display: 'none' }}
    accept={accept}
    {...dirProps}
    multiple={multiple}
    onChange={onChange}
    {...(capture != null ? { capture } : {})}
  />
  {slots.default?.()}
</Tag>
  • 看一下Tagevents事件,重点:onClick事件、onDrop事件。
const events = {
  onClick: openFileDialogOnClick ? onClick : () => {},
  onKeydown: openFileDialogOnClick ? onKeyDown : () => {},
  onMouseenter,
  onMouseleave,
  onDrop: onFileDrop,
  onDragover: onFileDrop,
  tabindex: '0',
};
  1. onClick事件:调用fileInput.value.click,打开文件选择框

    const onClick = (e: MouseEvent | KeyboardEvent) => {
      const el = fileInput.value;
      if (!el) {
        return;
      }
      const { onClick } = props;
     
      el.click();
      if (onClick) {
        onClick(e);
      }
    };
    
  2. onDrop / onDragover事件:onDragover指的是鼠标在目标区域内移动,这时候只阻止默认事;onDrop指的是在目标区域松开鼠标点击,这时候会处理上传。

    const onFileDrop = (e: DragEvent) => {
      const { multiple } = props;
    
      e.preventDefault();
    
      if (e.type === 'dragover') {
        return;
      }
    	/**
    	* 如果是文件夹,先处理文件树,然后调用第二个参数传递的uploadFiles,上传文件
    	*/
      if (props.directory) {
        traverseFileTree(
          Array.prototype.slice.call(e.dataTransfer.items),
          uploadFiles,
          (_file: RcFile) => attrAccept(_file, props.accept),
        );
      } else {
        /**
        * 如果选的是文件,上传选择成功的
        *(windows电脑可以选任意类型,但是上传的时候可能会被类型校验卡掉一些文件;mac只能选择类型匹配的文件)
        */
        const files: [RcFile[], RcFile[]] = partition(
          Array.prototype.slice.call(e.dataTransfer.files),
          (file: RcFile) => attrAccept(file, props.accept),
        );
        let successFiles = files[0];
        const errorFiles = files[1];
        if (multiple === false) {
          successFiles = successFiles.slice(0, 1);
        }
    
        uploadFiles(successFiles);
        if (errorFiles.length && props.onReject) props.onReject(errorFiles);
      }
    };
    

2.2 文件选择成功后的流程

文件选择成功后,会触发inputchange方法。主流程如下:

<Tag {...events} class={cls} role="button" style={attrs.style}>
  <input
		// ......
		onChange={onChange}
	/>
  {slots.default?.()}
</Tag>

在这里插入图片描述

2.2.1 onChange方法
 const onChange = (e: ChangeEvent) => {
   const { accept, directory } = props;
   const { files } = e.target as any;
   // 非文件夹且校验通过的文件
   const acceptedFiles = [...files].filter(
     (file: RcFile) => !directory || attrAccept(file, accept),
   );
   uploadFiles(acceptedFiles);
   reset();
 };

🚀 attrAccept方法,见 3.1

2.2.2 uploadFiles方法
 const uploadFiles = (files: File[]) => {
   const originFiles = [...files] as RcFile[];
   /**
   * 为每个文件生成一个id,调用processFile进行处理
   */
   const postFiles = originFiles.map((file: RcFile & { uid?: string }) => {
     file.uid = getUid();
     return processFile(file, originFiles);
   });

   /**
   * 所有文件处理完成后,回调onBatchStart方法,然后依次上传文件。
   */
   Promise.all(postFiles).then(fileList => {
     const { onBatchStart } = props;

     onBatchStart?.(fileList.map(({ origin, parsedFile }) => ({ file: origin, parsedFile })));

     fileList
       .filter(file => file.parsedFile !== null)
       .forEach(file => {
       post(file);
     });
   });
 };
2.2.3 processFile 方法
const processFile = async (file: RcFile, fileList: RcFile[]): Promise<ParsedFileInfo> => {
  const { beforeUpload } = props;

  /**
  * 1 调用用户传递的beforeUpload方法,如果这个方法返回false,则停止上传
  */
  let transformedFile: BeforeUploadFileType | void = file;
  if (beforeUpload) {
    try {
      transformedFile = await beforeUpload(file, fileList);
    } catch (e) {
      // Rejection will also trade as false
      transformedFile = false;
    }
    if (transformedFile === false) {
      return {
        origin: file,
        parsedFile: null,
        action: null,
        data: null,
      };
    }
  }

  /**
  * 2 action一般是上传地址,也可以是返回地址的函数
  */
  const { action } = props;
  let mergedAction: string;
  if (typeof action === 'function') {
    mergedAction = await action(file);
  } else {
    mergedAction = action;
  }

  /**
  * 3 上传所需参数或返回上传参数的方法
  */
  const { data } = props;
  let mergedData: Record<string, unknown>;
  if (typeof data === 'function') {
    mergedData = await data(file);
  } else {
    mergedData = data;
  }

  /**
  * 可以忽略,当作简单赋值语句即可
  */
  const parsedData =
        // string type is from legacy `transformFile`.
        // Not sure if this will work since no related test case works with it
        (typeof transformedFile === 'object' || typeof transformedFile === 'string') &&
        transformedFile
  ? transformedFile
  : file;

  /**
  * 4 最后的文件如果不是file类型,把它转换成file类型
  */
  let parsedFile: File;
  if (parsedData instanceof File) {
    parsedFile = parsedData;
  } else {
    parsedFile = new File([parsedData], file.name, { type: file.type });
  }

  const mergedParsedFile: RcFile = parsedFile as RcFile;
  mergedParsedFile.uid = file.uid;

  /**
  * 5 最后的file,叫parsedFile
  */
  return {
    origin: file,
    data: mergedData,
    parsedFile: mergedParsedFile,
    action: mergedAction,
  };
};
2.2.4 post方法

request 方法见3.2。

const post = ({ data, origin, action, parsedFile }: ParsedFileInfo) => {
  if (!isMounted) {
    return;
  }

  const { onStart, customRequest, name, headers, withCredentials, method } = props;

  const { uid } = origin;
  /**
  * 可以使用自定义的上传函数,默认使用request.ts提供的上传方法
  */
  const request = customRequest || defaultRequest;

  const requestOption = {
    action,
    filename: name,
    data,
    file: parsedFile,
    headers,
    withCredentials,
    method: method || 'post',
    onProgress: (e: UploadProgressEvent) => {
      const { onProgress } = props;
      onProgress?.(e, parsedFile);
    },
    onSuccess: (ret: any, xhr: XMLHttpRequest) => {
      const { onSuccess } = props;
      onSuccess?.(ret, parsedFile, xhr);

      delete reqs[uid];
    },
    onError: (err: UploadRequestError, ret: any) => {
      const { onError } = props;
      onError?.(err, ret, parsedFile);

      delete reqs[uid];
    },
  };

  onStart(origin);
  /**
  * reqs 是一个全局的对象,方法调用abort方法。
  */
  reqs[uid] = request(requestOption);
};

3 辅助函数

3.1 检查文件类型

源码地址:https://github.com/vueComponent/ant-design-vue/blob/main/components/vc-upload/attr-accept.ts

使用 some 函数来判断,只要文件file匹配了accept数组的某一项规则,则验证通过。

在这里插入图片描述

export default (file: RcFile, acceptedFiles: string | string[]) => {
  if (file && acceptedFiles) {
    const acceptedFilesArray = Array.isArray(acceptedFiles)
      ? acceptedFiles
      : acceptedFiles.split(',');
    const fileName = file.name || '';
    const mimeType = file.type || '';
    const baseMimeType = mimeType.replace(/\/.*$/, ''); // 把“.“以及之后的所有字符清空

    return acceptedFilesArray.some(type => {
      const validType = type.trim();
      // 如果validType是*/*或者*,那么所有文件都通过
      if (/^\*(\/\*)?$/.test(type)) { // 以 * 开头,后面可以接0个或者1个 /*
        return true;
      }

      // 如果validType是 .jpg .png之类的,检查文件名后缀
      if (validType.charAt(0) === '.') {
        const lowerFileName = fileName.toLowerCase();
        const lowerType = validType.toLowerCase();

        let affixList = [lowerType];
        if (lowerType === '.jpg' || lowerType === '.jpeg') {
          affixList = ['.jpg', '.jpeg'];
        }

        return affixList.some(affix => lowerFileName.endsWith(affix));
      }

      // 如果validType是image/*之类的,那么比较 baseMimeType 和 斜杠之前的部分
      if (/\/\*$/.test(validType)) {
        return baseMimeType === validType.replace(/\/.*$/, ''); // 把“.“以及之后的所有字符清空
      }

      // 类型完全匹配,通过
      if (mimeType === validType) {
        return true;
      }

      // 验证规则无效,也通过
      if (/^\w+$/.test(validType)) {  // \w表示数字和字符,+表示1个及以上
        warning(false, `Upload takes an invalidate 'accept' type '${validType}'.Skip for check.`);
        return true;
      }

      return false;
    });
  }
  return true;
};

3.2 xhr上传文件

源码地址:https://github.com/vueComponent/ant-design-vue/blob/main/components/vc-upload/request.ts

针对单个文件,调用upload方法,把option对象传进去,对象如下示例:

在这里插入图片描述

整体过程:

在这里插入图片描述

export default function upload(option: UploadRequestOption) {
  const xhr = new XMLHttpRequest();

  if (option.onProgress && xhr.upload) {
    /**
    * 上传过程会实时计算进度,通过onProgress回调返回给用户
    */
    xhr.upload.onprogress = function progress(e: UploadProgressEvent) {
      if (e.total > 0) {
        e.percent = (e.loaded / e.total) * 100;
      }
      option.onProgress(e);
    };
  }

  /**
  * FormData这种格式会自动修改'content-type'
  */
  const formData = new FormData();

  /**
  * data是用户自定义的属性,或者自定义的方法的返回值
  */
  if (option.data) {
    Object.keys(option.data).forEach(key => {
      const value = option.data[key];
      // support key-value array data
      if (Array.isArray(value)) {
        value.forEach(item => {
          // { list: [ 11, 22 ] }
          // formData.append('list[]', 11);
          formData.append(`${key}[]`, item);
        });
        return;
      }

      formData.append(key, value as string | Blob);
    });
  }

  /**
  * 用户上传的文件
  */
  if (option.file instanceof Blob) {
    formData.append(option.filename, option.file, (option.file as any).name);
  } else {
    formData.append(option.filename, option.file);
  }

  xhr.onerror = function error(e) {
    option.onError(e);
  };

  xhr.onload = function onload() {
    // 只有2xx认为是成功的
    if (xhr.status < 200 || xhr.status >= 300) {
      return option.onError(getError(option, xhr), getBody(xhr));
    }

    return option.onSuccess(getBody(xhr), xhr);
  };

  /**
  * 设置请求的方法、url、是否异步,这里的true代表异步
  */
  xhr.open(option.method, option.action, true);

  /**
  * 可以通过设置 withCredentials 属性为 true 来启用 cookies 和 HTTP 认证信息的发送
  */
  // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
  if (option.withCredentials && 'withCredentials' in xhr) {
    xhr.withCredentials = true;
  }

  const headers = option.headers || {};

  // when set headers['X-Requested-With'] = null , can close default XHR header
  // see https://github.com/react-component/upload/issues/33
  if (headers['X-Requested-With'] !== null) {
    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  }

  /**
  * 设置所有的header
  */
  Object.keys(headers).forEach(h => {
    if (headers[h] !== null) {
      xhr.setRequestHeader(h, headers[h]);
    }
  });

  /**
  * 发送请求
  */
  xhr.send(formData);

  return {
    /**
    * 返回取消的方法,所以上传过程是可以中断的
    */
    abort() {
      xhr.abort();
    },
  };
}

4 总结

上传文件的每个步骤都已经在上文中体现,除了处理文件树的部分。剩下UploadDraggervc-upload的封装,下篇文章再进行分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2126340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2. 变量和指令(omron 机器自动化控制器)——1

机器自动化控制器——第二章 变量和指令 1 2-1 变量一览表MC通用变量轴变量▶ 轴组变量 运动控制指令的输入变量输入变量的有效范围▶ 枚举体一览表 运动控制指令的输出变量运动控制指令的输入输出变量 2-1 变量一览表 MC功能模块使用的变量分为两类。 一类是监视轴等的状态及…

【SQL】百题计划:SQL对于空值的比较判断。

[SQL]百题计划 方法&#xff1a; 使用 <> (!) 和 IS NULL [Accepted] 想法 有的人也许会非常直观地想到如下解法。 SELECT name FROM customer WHERE referee_Id <> 2;然而&#xff0c;这个查询只会返回一个结果&#xff1a;Zach&#xff0c;尽管事实上有 4 个…

关于使用HAWQ量化框架执行训练前推理的性能崩溃问题

问题描述 今天debug量化模型遇到一个比较奇怪的问题&#xff0c;之前从来没有注意过&#xff1a; 现在量化模型的流程是&#xff1a; 1&#xff09;加载预训练好的浮点数权重模型&#xff1b; 2&#xff09;将模型架构替换成量化架构&#xff08;逐模块替换&#xff09;&#…

Linux和C语言(Day11)

一、学习内容 讲解有参函数 形参 和 实参 形参——定义时的参数&#xff0c;形式上的参数&#xff0c;没有实际意义&#xff0c;语法上必须带有数据类型 void fun(int a,int b); void fun(int a[],int n); void fun(char *s); 可以是&#xff1a;变量、数组、指针 实参——调用…

【笔记】物理化学绪论

文章目录 1. 物理化学的目的和研究内容什么是物理化学&#xff1f;目的内容 2. 物理化学的学习方法3. 物理量的表示和运算&#xff08;1&#xff09;物理量的表示&#xff08;2&#xff09;量值计算 4. 课程安排 1. 物理化学的目的和研究内容 用物理变化 P、V、T热效应电效应…

【数据结构】排序算法系列——序言(附源码+图解)

作为基础算法的中流砥柱部分&#xff0c;排序算法一直都是计算机学习者们不可忽略的一部分。而其中的算法思想也蕴含着许多在今后的算法学习甚至是整个计算机技术的学习之中仍然熠熠生辉的算法思想&#xff0c;它们引领着我们不断探索算法的奥秘之处。所以&#xff0c;学习排序…

黑神话悟空大圣残躯怎么打 头目大圣残躯攻略

​面对《黑神话&#xff1a;悟空》中的终极挑战——大圣残躯&#xff0c;掌握其打法要点&#xff0c;是通往胜利的关键。下面&#xff0c;就让我们一步步解析如何战胜这位强大的最终BOSS吧。 一、BOSS位置 随主线流程必解锁。击败石猿后&#xff0c;齐天大圣的真身——大圣残躯…

IIC通信中设备的交互流程

本文主要叙述&#xff0c;当两个设备进行 IIC 通信时&#xff0c;两个设备的交互流程&#xff0c;即主机的动作和从机的动作。当通过软件编程的方式实现设备间的 IIC 通信时&#xff0c;我们就是按照主机的动作或从机的动作来编写对应的代码。实际上&#xff0c;主机和从机是按…

小怡分享之栈和队列

前言&#xff1a; &#x1f308;✨前面小怡给大家分享了顺序表和链表&#xff0c;今天小怡给大家分享一下栈和队列。 1.栈 1.1 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#x…

聚焦:clicOH 借助 NVIDIA cuOpt 实现最后一英里交付速度 20 倍提升

受消费者行为转变和疫情影响&#xff0c;电子商务继续呈爆炸式增长和转型。因此&#xff0c;物流和运输公司发现自己处于包裹配送革命的前沿。这新的现实情况在最后一英里配送中尤为明显&#xff0c;而后者现在已经成为供应链物流中成本最高的要素&#xff0c;占从零售到制造等…

Learn ComputeShader 13 Adding a mesh to each particle

这次要给每个粒子加上网格。 添加的网格只是一个简单的四边形&#xff0c;它需要分成两个三角形。并且三角形的顶点必须按照顺时针排列&#xff0c;同时每个顶点都应该有UV信息。 所以接下来就要添加顶点结构体以及相关的compute buffer struct Vertex{public Vector3 positi…

大数据新视界 --大数据大厂之 Spark 性能优化秘籍:从配置到代码实践

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

数据结构————栈的讲解(超详细!!!)

1 栈的概念和结构 1.1 栈的概念 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作&#xff0c;进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵循后进先出&#xff08;先进后出&#xff09;原则。特性与栈的…

杂七杂八-必备软件下载

必备软件下载 学术软件工作软件tips软件 仅个人笔记使用&#xff0c;后续持续更新&#xff0c;感谢点赞关注 学术软件 幕布&#xff1a;记录各种笔记&#xff0c;文本和思维导图快捷互换边界AIchat&#xff1a;集成了多个最新大模型工具&#xff0c;功能丰富&#xff0c;推荐使…

html加载页面

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>算数模一体化</title> </head><b…

GPT-4论文阅读

GPT-4 Technical Report论文阅读 文章目录 GPT-4 Technical Report论文阅读 Abstract训练的稳定性Training processPredictable scaling训练的稳定性多么难能可贵 Capabilities考试成绩传统的benchmark语言方面的能力Visual inputsSteerability LimitationsRisks & mitigat…

初识Linux · 进程(2)

目录 前言&#xff1a; 有关进程的相关理解 前言&#xff1a; 本文会开始慢慢切入进程了&#xff0c;当然&#xff0c;切入进程之前&#xff0c;我们需要再次复习一下操作系统&#xff0c;后面接着是介绍什么是进程&#xff0c;如何查看进程&#xff0c;在Linux中对应的文件…

你真的了解电阻吗

电阻通常有下面几种表示符号。 我们用的最多的还是定值电阻。 ESP32参考设计原理图用的是折线。 GD32中参考设计原理图用的是小方框。 包括我们画原理图的时候用的基本也是小方框。 折线符号属于ANSI&#xff08;美国标准&#xff09;矩形符号属于DIN标准&#xff08;德国工业…

Linux基本

一、安装 &#xff08;一&#xff09;bios basic input / output system cpu虚拟化技术需要开启 intel amd 不同品牌进入bios快捷键不一样 &#xff08;二&#xff09;vmware 新建 配置硬件 硬盘 建议单个虚拟硬盘文件&#xff0c;比较好管理 r如果有转移的需求&#xff…

freertos 任务调度—抢占式, 时间片

FreeRTOS 操作系统支持三种调度方式&#xff1a; 抢占式调度&#xff0c;时间片调度和合作式调度。 实际应用主要是抢占式调度和时间片调度&#xff0c;合作式调度用到的很少. 1,抢占式调度 每个任务都有不同的优先级&#xff0c; 任务会一直运行直到被高优先级任务抢占或者遇到…