如何优雅地实现文件上传+文件夹上传+拖拽上传+进度追踪+...?
需求分析:
显示
上传文件或文件夹的名字
、类型
、大小
、状态
。
类型
MIME
。大小
状态
最少应该有的状态(假设上传一定成功,不会出现错误)
其他扩展状态
disabled
,并且在没有文件上传的时候点击是不会触发上传的(可以在此时给出一个提醒)文件选中方式
input[type="file"]
来选中进度追踪
loaded/total
。loaded
之和除以所有文件的total
之和。暂停
中断重试
文件上传性能优化
我自己用react+tailwindcss
尝试勉强实现了基础功能。
维护了一个树结构来记录文件上传的状态。
其中每个节点的字段含义
type
0
表示文件, 1
表示文件夹file
File
: 对于文件来说是File对象
,记录了文件的一些信息。{ name: <directory name>, size: <directory size>, type: "文件夹"}
: 对于文件夹来说只是记录了文件夹的名字、大小和类型。children
:
parent
[node, idx]
: node
表示该节点的父节点,idx
表示该节点在其父节点的children
中位于第几个。
问题: 如果我们想要删除某一个节点(相当于取消某个文件的上传)
方案1 splice
parent
也要改变,所有依赖idx
的数据也改变。idx
,否则渲染的顺序就发生变化了。)方案2 假的删除
children数组
的对应位置(idx
)设置为null
,表示该节点已经被删除。null
: 表明该节点为根节点。progress
:
num
: 对于文件来说,它就是一个数值。由XMLHttpRequest
实例的中的upload
对象上progress
事件中得到的event.target
和event.loaded
计算而来得到。(loaded * 100 /tatoal
)。{loaded: [...], total: [....]}
: 对于文件夹来说,我们需要统计其子节点的loaded
和toatal
之后再计算得到其最终的进度。
parent
,去修改其祖先节点中progress.loaded
和progress.total
。(这就是我们需要parent
字段的原因)问题:这棵树在该react
项目中为一个状态,应该保持它的不可变性。对于这种复杂的数据结构该怎么保持它的不可变性呢?借助immer
?
下面为更新进度的过程,该怎么修改保持不可变性呢?
// src/utils.jsexport function uploadProcess(uploadFileList, setUploadFileList, setStatus) { // TopNode用作哨兵,让边界条件处理更容易。 for (let i = 0; i < uploadFileList.length; ++i) { uploadFileList[i].parent = [TopNode, i]; TopNode.children.push(uploadFileList[i]); } TopNode.progress = { loaded: Array(uploadFileList.length).fill(0), total: Array(uploadFileList.length).fill(0), }; uploadFileOrDirectory( TopNode, () => { setUploadFileList([...uploadFileList]); }, setStatus );}function uploadFileOrDirectory(entry, update, setStatus) { if (entry === null) return; if (entry.type === 0) { const xhr = new XMLHttpRequest(); const data = new FormData(); data.append("file", entry.file); xhr.open("POST", UPLOAD_URL); xhr.upload.addEventListener("progress", (e) => { const percent = Number(((e.loaded * 100) / e.total).toFixed(0)); entry.progress = percent; propagateProcessUpward(entry, e.loaded, e.total, setStatus); update(); }); xhr.send(data); } else { for (const e of entry.children) { uploadFileOrDirectory(e, update, setStatus); } }}function propagateProcessUpward(entry, loaded, total, setStatus) { if (entry.parent) { const [parent, idx] = entry.parent; parent.progress.loaded[idx] = loaded; parent.progress.total[idx] = total; propagateProcessUpward( parent, parent.progress.loaded.reduce((acc, cur) => acc + cur, 0), parent.progress.total.reduce((acc, cur) => acc + cur, 0), setStatus ); } else { // topNode if ( entry.progress.loaded.every( (item, idx) => entry.progress.total[idx] === item ) ) setStatus(2); }}
附:
issue
里提)import produce from "immer";const initialState = { files: []};const uploadReducer = produce((draft, action) => { switch (action.type) { case 'ADD_FILE': draft.files.push(action.payload); break; case 'UPDATE_PROGRESS': const file = draft.files.find(file => file.id === action.payload.id); if (file) { file.progress = action.payload.progress; } break; default: break; }}, initialState);function updateProgress(id, progress) { return { type: 'UPDATE_PROGRESS', payload: { id, progress } };}
你的实现已经包含了大部分需要的功能,并且你对数据结构的设计也很到位。然而,确实有一些可以改进和优化的地方。以下是我对你的问题的一些解答和建议:
问题1:如何优雅地删除和恢复节点?
一种更优雅的方法是使用一种称为"路径枚举"或"路径压缩"的技术。你可以将每个节点的路径(从根节点到该节点的所有父节点的索引)存储在每个节点中,而不是仅仅存储父节点的索引。这样,当你需要删除或恢复一个节点时,你只需要更新该节点的路径,而不需要更新其所有祖先节点的子节点。这种方法可以在O(1)的时间内完成删除和恢复操作,而不需要遍历整个树。
问题2:如何保持数据结构的不可变性?
对于保持数据结构的不可变性,你可以使用一种称为"函数式编程"的技术。在这种编程风格中,你永远不会直接修改数据,而是创建新的数据来替代旧的数据。在JavaScript中,你可以使用immer
库来帮助你实现函数式编程。immer
允许你以一种更直观、更易于理解的方式创建新的数据,而不需要手动创建所有的新数据。
问题3:如何更新进度并保持数据结构的不可变性?
对于更新进度,你可以将进度信息存储在每个节点中,并在上传过程中更新这些信息。当你需要更新整个数据结构的进度时,你可以遍历整个树并收集所有节点的进度信息。然后,你可以使用这些信息来创建一个新的数据结构,其中包含最新的进度信息。
下面是一个简化的示例代码,展示了如何使用immer
和函数式编程来更新进度并保持数据结构的不可变性:
import produce from "immer";const initialState = { // 你的初始状态};function updateProgress(draftState, entry, loaded, total) { const traverse = (node) => { if (node.type === 0) { node.progress = (loaded * 100) / total; } else { node.progress = { loaded: node.children.reduce((acc, cur) => acc + cur.progress.loaded, 0), total: node.children.reduce((acc, cur) => acc + cur.progress.total, 0), }; node.progress.loaded = (node.progress.loaded * 100) / node.progress.total; node.children.forEach(traverse); } }; traverse(draftState.find((node) => node.id === entry.id));}function handleUpload(state, action) { return produce(state, (draftState) => { switch (action.type) { case "UPDATE_PROGRESS": updateProgress(draftState, action.payload.entry, action.payload.loaded, action.payload.total); break; // 处理其他动作 default: break; } });}// 在你的组件中使用handleUpload来更新状态
在这个示例中,updateProgress
函数使用递归来遍历树并更新每个节点的进度。handleUpload
函数使用immer
的produce
函数来创建一个新的状态,其中包含最新的进度信息。
请注意,这只是一个简化的示例,你可能需要根据你的具体需求来修改和扩展它。另外,你可能还需要处理其他类型的动作,如添加、删除和恢复节点等。你可以使用类似的方法来处理这些动作,以保持数据结构的不可变性。
本文向大家介绍简单实现ajax拖拽上传文件,包括了简单实现ajax拖拽上传文件的使用技巧和注意事项,需要的朋友参考一下 AJAX拖拽上传功能实现,供大家参考,具体内容如下 //server.php 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。
本文向大家介绍如何实现文件拖动上传?相关面试题,主要包含被问及如何实现文件拖动上传?时的应答技巧和注意事项,需要的朋友参考一下 利用 HTML5 的 API 来实现。需要注意的是,必须要设置 事件,不然不会触发 事件。 示例:https://codepen.io/Konata9/pen/BaBzExe?editors=1011
问题内容: 看来我还没有清楚地传达出我的问题。我需要发送一个文件(使用AJAX),并且需要使用Nginx HttpUploadProgressModule 获取文件的上传进度。我需要一个很好的解决方案。我已经尝试过使用jquery.uploadprogress插件,但是我发现自己不得不重写其中的大部分内容,以使其在所有浏览器中都能正常工作并使用AJAX发送文件。 我所需要的只是执行此操作的代码,它
本文向大家介绍PHP实现文件上传和多文件上传,包括了PHP实现文件上传和多文件上传的使用技巧和注意事项,需要的朋友参考一下 在PHP程序开发中,文件上传是一个使用非常普遍的功能,也是PHP程序员的必备技能之一。值得高兴的是,在PHP中实现文件上传功能要比在Java、C#等语言中简单得多。下面我们结合具体的代码实例来详细介绍如何通过PHP实现文件上传和多文件上传功能。 要使用PHP实现文件上传功能,
本文向大家介绍jQuery插件实现文件上传功能(支持拖拽),包括了jQuery插件实现文件上传功能(支持拖拽)的使用技巧和注意事项,需要的朋友参考一下 先贴上源代码地址,点击获取。然后直接进入主题啦,当然,如果你觉得我有哪里写的不对或者欠妥的地方,欢迎留言指出。在附上一些代码之前,我们还是先来了解下,上传文件的时候需要利用的一些必要的知识。 首先我们要说的就是FileReader对象,这是一个H
问题内容: 当用户将文件上传到我的Web应用程序时,我想显示比gif动画更有意义的内容。我有什么可能性? 编辑:我正在使用.Net,但我不介意是否有人向我展示平台不可知的版本。 问题答案: 以下是一些常用JavaScript工具包的几种版本。 Mootools- http: //digitarald.de/project/fancyupload/ Extjs- http: //extjs.com/