具有拖放功能的响应式 React 文件上传组件
在开发一个 React 项目时,我实现了一个响应式文件上传组件,该组件无需使用任何库即可支持拖放功能。网上大多数文件上传组件都使用诸如react-dropzone之类的库来支持拖放功能。因此,我想分享一下我是如何制作这个组件的,并展示一个典型的用例。
最终结果


其特点包括:
- 无需使用任何库即可进行拖放
- 显示图像文件的图像预览
- 显示文件大小和名称
- 删除“待上传”部分中的文件
- 阻止用户上传大于指定大小的文件
- 注意:出于安全原因,这也应该在后端完成
项目设置
先决条件:Node(用于安装 npm 包)
如果你熟悉构建 React 应用程序,那么设置新 React 项目最简单的方法是使用create-react-app。因此,在终端/命令行中运行以下命令:
npx create-react-app react-file-upload
cd react-file-upload
为了确保运行后一切都设置正确,当您在浏览器中npm start
访问时应该出现以下内容:localhost:3000
在构建组件之前,让我们修改和删除一些文件以摆脱不必要的代码。
- 更改
App.js
为以下内容:
import React from 'react';
function App() {
return (
<div></div>
);
}
export default App;
- 更改
index.js
为以下内容:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
删除src
文件夹中除以下文件之外的所有文件
App.js
index.js
index.css
文件上传组件
安装依赖项
我们需要的依赖项是:
- 用于设置组件的样式
- 样式组件允许样式封装并通过 props 创建动态样式
- 用于编译样式组件中使用的 Sass 样式(可选,可以使用 CSS)
要安装它们,请运行npm i styled-components node-sass
。
文件夹结构
构建文件夹和文件的一个好习惯是创建一个 components 文件夹,每个组件都对应一个文件夹。这样可以更轻松地找到每个组件的逻辑和样式。
按照此约定,在文件夹中创建一个组件文件夹src
,然后在文件夹中创建一个文件上传文件夹components
。
最后,在文件上传文件夹中,创建 2 个新文件。
file-upload.component.jsx
file-upload.styles.js
状态
由于我们正在创建一个功能组件并且需要使用状态,因此我们将使用useState 钩子。
useState 钩子返回一个状态值,该值与作为第一个参数传递的值相同,以及一个用于更新它的函数。
为了达到我们的目的,我们需要状态来跟踪上传的文件。因此,在file-upload.component.jsx
文件中添加以下内容:
import React, { useState } from "react";
const FileUpload = () => {
const [files, setFiles] = useState({});
return (
<div></div>
)
}
export default FileUpload;
“我们不应该使用空数组而不是空对象来表示files
状态吗?”
使用对象可以让我们轻松地操作(添加/删除)files
状态,并防止同名文件被多次上传。以下是状态的示例files
:
{
"file1.png": File,
"file2.png": File
}
如果使用数组,则需要更多工作。例如,要删除一个文件,我们必须遍历每个文件,直到找到要删除的文件。
注意:File 是一个 JS 对象。更多信息请访问https://developer.mozilla.org/en-US/docs/Web/API/File。
useRef 钩子
如果你查看上面的图 1,你会注意到用户可以拖放文件,也可以点击“上传文件”按钮。默认情况下,点击文件输入标签后会打开文件资源管理器。但是,我们希望在点击“上传文件”按钮后打开文件资源管理器,因此我们需要一个指向文件输入标签的 DOM 引用。
要创建 DOM 引用,我们将使用useRef 钩子。 useRef 钩子返回一个可变的 ref 对象,该对象.current
属性指向一个 DOM 节点(在本例中为文件输入标签)。
一旦我们使用 useRef 钩子,我们必须将返回值传递给文件输入标签的 ref 属性,如下所示:
import React, { useState, useRef } from "react";
const FileUpload = (props) => {
const fileInputField = useRef(null);
const [files, setFiles] = useState({});
return (
<input type="file" ref={fileInputField} />
)
}
export default FileUpload;
道具
该组件将具有以下属性:
label
- 确定组件的标签(例如,上图 1 中的“配置文件图像”)
maxFileSizeInBytes
- 防止上传超过指定大小的文件
updateFilesCb
- 用于将
files
状态发送给父组件的回调函数
- 用于将
“为什么我们需要将files
状态发送给父组件?”
通常,文件上传组件会在表单中使用,在 React 中使用表单时,组件会将表单数据存储在 state 中。因此,为了让父组件也能存储上传的文件,我们需要文件上传组件来发送它。
“为什么我们需要使用回调函数将files
状态发送给父组件?”
由于 React 具有单向数据流,我们无法轻松地将数据从子组件(文件上传组件)传递到父组件。为了解决这个问题,我们将传递一个在父组件中声明的函数,然后文件上传组件将使用状态files
作为参数来调用该函数。关于如何将数据从子组件发送到父组件的更多解释,请参阅https://medium.com/@jasminegump/passing-data-between-a-parent-and-child-in-react-deea2ec8e654。
使用解构,我们现在可以像这样添加 props:
import React, { useRef, useState } from "react";
const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const FileUpload = ({
label,
updateFilesCb,
maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
...otherProps
}) => {
const fileInputField = useRef(null);
const [files, setFiles] = useState({});
return (
<input type="file" ref={fileInputField} />
)
}
export default FileUpload;
“为什么我们在解构时要使用扩展语法otherProps
?”
在解构时,我们可以将未明确解构的所有其他值分配给变量。
let props = { a: 1, b: 2, c: 3};
let {a, ...otherProps} = props;
//a = 1
//otherProps = {b: 2, c: 3};
在这种情况下,任何未经解构的 props 都会被赋值给该otherProps
变量。稍后我们会看到这个变量的用法otherProps
。
HTML
对于图 1 所示的图标,我们将使用 Font Awesome。要导入它,请在文件的head 标签public/index.html
中添加以下内容:
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css"
/>
从图 1 可以看出,我们可以将组件的 HTML 分为两个主要部分。
这是包含第一部分 HTML 的组件:
import React, { useRef, useState } from "react";
const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const FileUpload = ({
label,
updateFilesCb,
maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
...otherProps
}) => {
const fileInputField = useRef(null);
const [files, setFiles] = useState({});
return (
<section>
<label>{label}</label>
<p>Drag and drop your files anywhere or</p>
<button type="button">
<i className="fas fa-file-upload" />
<span> Upload {otherProps.multiple ? "files" : "a file"}</span>
</button>
<input
type="file"
ref={fileInputField}
title=""
value=""
{...otherProps}
/>
</section>
);
}
export default FileUpload;
之前我们讨论过,任何未经解构的 props 都会被赋值给变量(即除、、otherProps
之外的任何 prop )。在上面的代码中,我们将该变量传递给文件输入标签。这样做是为了让我们能够通过 props 从父组件向文件输入标签添加属性。label
updateFilesCb
maxFileSizeInBytes
otherProps
“为什么我们要将标题和值属性设置为""
?”
设置标题属性可以""
删除鼠标悬停在输入标签上时默认显示的文本(“未选择文件”)。
将 value 属性设置为 可以""
修复一个极端情况:在删除文件后立即上传文件不会改变files
状态。稍后我们将看到,files
只有当 input 标签的值发生变化时,状态才会改变。出现此错误是因为当我们删除文件时,input 标签的值不会改变。由于状态更改会重新渲染 HTML,因此将 value 属性设置为 会""
在每次状态更改时重置 input 标签的值files
。
在编写第二部分的 HTML 之前,请记住 React 只允许从组件返回一个父元素<></>
。因此,我们将两个部分都放在一个标签中。
以下是包含两个部分的 HTML 的组件:
import React, { useRef, useState } from "react";
const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const KILO_BYTES_PER_BYTE = 1000;
const convertBytesToKB = (bytes) => Math.round(bytes / KILO_BYTES_PER_BYTE);
const FileUpload = ({
label,
updateFilesCb,
maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
...otherProps
}) => {
const fileInputField = useRef(null);
const [files, setFiles] = useState({});
return (
<>
<section>
<label>{label}</label>
<p>Drag and drop your files anywhere or</p>
<button type="button">
<i className="fas fa-file-upload" />
<span> Upload {otherProps.multiple ? "files" : "a file"}</span>
</button>
<input
type="file"
ref={fileInputField}
title=""
value=""
{...otherProps}
/>
</section>
{/*second part starts here*/}
<article>
<span>To Upload</span>
<section>
{Object.keys(files).map((fileName, index) => {
let file = files[fileName];
let isImageFile = file.type.split("/")[0] === "image";
return (
<section key={fileName}>
<div>
{isImageFile && (
<img
src={URL.createObjectURL(file)}
alt={`file preview ${index}`}
/>
)}
<div isImageFile={isImageFile}>
<span>{file.name}</span>
<aside>
<span>{convertBytesToKB(file.size)} kb</span>
<i className="fas fa-trash-alt" />
</aside>
</div>
</div>
</section>
);
})}
</section>
</article>
</>
);
};
export default FileUpload;
在 HTML 的第二部分,我们遍历状态中的每个文件并显示文件名、大小(以 KB 为单位)以及文件类型(即 png、jpg 等)的files
图像预览。image/*
为了显示图像预览,我们使用了URL.createObjectURL
createObjectURL 函数。createObjectURL 函数接受一个对象(在本例中是一个File对象),并返回一个用于访问该文件的临时 URL。然后,我们可以将该 URL 设置src
为 img 标签的属性。
造型
我们现在将使用之前安装的 styled-components 包。
在文件中添加以下内容file-upload.styles.js
:
import styled from "styled-components";
export const FileUploadContainer = styled.section`
position: relative;
margin: 25px 0 15px;
border: 2px dotted lightgray;
padding: 35px 20px;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
background-color: white;
`;
export const FormField = styled.input`
font-size: 18px;
display: block;
width: 100%;
border: none;
text-transform: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
&:focus {
outline: none;
}
`;
export const InputLabel = styled.label`
top: -21px;
font-size: 13px;
color: black;
left: 0;
position: absolute;
`;
export const DragDropText = styled.p`
font-weight: bold;
letter-spacing: 2.2px;
margin-top: 0;
text-align: center;
`;
export const UploadFileBtn = styled.button`
box-sizing: border-box;
appearance: none;
background-color: transparent;
border: 2px solid #3498db;
cursor: pointer;
font-size: 1rem;
line-height: 1;
padding: 1.1em 2.8em;
text-align: center;
text-transform: uppercase;
font-weight: 700;
border-radius: 6px;
color: #3498db;
position: relative;
overflow: hidden;
z-index: 1;
transition: color 250ms ease-in-out;
font-family: "Open Sans", sans-serif;
width: 45%;
display: flex;
align-items: center;
padding-right: 0;
justify-content: center;
&:after {
content: "";
position: absolute;
display: block;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 100%;
background: #3498db;
z-index: -1;
transition: width 250ms ease-in-out;
}
i {
font-size: 22px;
margin-right: 5px;
border-right: 2px solid;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 20%;
display: flex;
flex-direction: column;
justify-content: center;
}
@media only screen and (max-width: 500px) {
width: 70%;
}
@media only screen and (max-width: 350px) {
width: 100%;
}
&:hover {
color: #fff;
outline: 0;
background: transparent;
&:after {
width: 110%;
}
}
&:focus {
outline: 0;
background: transparent;
}
&:disabled {
opacity: 0.4;
filter: grayscale(100%);
pointer-events: none;
}
`;
export const FilePreviewContainer = styled.article`
margin-bottom: 35px;
span {
font-size: 14px;
}
`;
export const PreviewList = styled.section`
display: flex;
flex-wrap: wrap;
margin-top: 10px;
@media only screen and (max-width: 400px) {
flex-direction: column;
}
`;
export const FileMetaData = styled.div`
display: ${(props) => (props.isImageFile ? "none" : "flex")};
flex-direction: column;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 10px;
border-radius: 6px;
color: white;
font-weight: bold;
background-color: rgba(5, 5, 5, 0.55);
aside {
margin-top: auto;
display: flex;
justify-content: space-between;
}
`;
export const RemoveFileIcon = styled.i`
cursor: pointer;
&:hover {
transform: scale(1.3);
}
`;
export const PreviewContainer = styled.section`
padding: 0.25rem;
width: 20%;
height: 120px;
border-radius: 6px;
box-sizing: border-box;
&:hover {
opacity: 0.55;
${FileMetaData} {
display: flex;
}
}
& > div:first-of-type {
height: 100%;
position: relative;
}
@media only screen and (max-width: 750px) {
width: 25%;
}
@media only screen and (max-width: 500px) {
width: 50%;
}
@media only screen and (max-width: 400px) {
width: 100%;
padding: 0 0 0.4em;
}
`;
export const ImagePreview = styled.img`
border-radius: 6px;
width: 100%;
height: 100%;
`;
使用 styled-components 时,我们创建的组件会渲染带有某些样式的 HTML 标签。例如,ImagePreview
是一个组件,它会渲染带有标签模板字面量img
中样式的标签。
由于我们正在创建组件,因此我们可以将 props 传递给它并在编写样式时访问它(例如FileMetaData
上面的示例)。
我们现在已经完成了样式设置和添加拖放功能。
“但是等等,我们什么时候添加拖放功能?”
默认情况下,文件输入标签支持拖放。我们只需设置输入标签的样式,并使其绝对定位即可(参考FormField
上文)。
要使用我们编写的样式,请导入所有样式组件并替换file-upload.component.jsx
文件中的 HTML。
import React, { useRef, useState } from "react";
import {
FileUploadContainer,
FormField,
DragDropText,
UploadFileBtn,
FilePreviewContainer,
ImagePreview,
PreviewContainer,
PreviewList,
FileMetaData,
RemoveFileIcon,
InputLabel
} from "./file-upload.styles";
const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const KILO_BYTES_PER_BYTE = 1000;
const convertBytesToKB = (bytes) =>
Math.round(bytes / KILO_BYTES_PER_BYTE);
const FileUpload = ({
label,
updateFilesCb,
maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
...otherProps
}) => {
const fileInputField = useRef(null);
const [files, setFiles] = useState({});
return (
<>
<FileUploadContainer>
<InputLabel>{label}</InputLabel>
<DragDropText>Drag and drop your files anywhere or</DragDropText>
<UploadFileBtn type="button">
<i className="fas fa-file-upload" />
<span> Upload {otherProps.multiple ? "files" : "a file"}</span>
</UploadFileBtn>
<FormField
type="file"
ref={fileInputField}
title=""
value=""
{...otherProps}
/>
</FileUploadContainer>
<FilePreviewContainer>
<span>To Upload</span>
<PreviewList>
{Object.keys(files).map((fileName, index) => {
let file = files[fileName];
let isImageFile = file.type.split("/")[0] === "image";
return (
<PreviewContainer key={fileName}>
<div>
{isImageFile && (
<ImagePreview
src={URL.createObjectURL(file)}
alt={`file preview ${index}`}
/>
)}
<FileMetaData isImageFile={isImageFile}>
<span>{file.name}</span>
<aside>
<span>{convertBytesToKB(file.size)} kb</span>
<RemoveFileIcon
className="fas fa-trash-alt"
/>
</aside>
</FileMetaData>
</div>
</PreviewContainer>
);
})}
</PreviewList>
</FilePreviewContainer>
</>
);
}
export default FileUpload;
功能
我们几乎完成了文件上传组件,我们只需要添加函数以便files
可以修改状态。
之前我们使用 useRef 钩子创建了一个 DOM 引用。现在,我们将使用它在点击“上传文件”按钮时打开文件资源管理器。为此,请在组件中添加以下函数:
const handleUploadBtnClick = () => {
fileInputField.current.click();
};
我们还需要给组件添加一个onClick
属性UploadFileBtn
来触发上述功能。
<UploadFileBtn type="button" onClick={handleUploadBtnClick}>
为了处理用户点击“上传文件”按钮后选择的文件,我们需要onChange
向组件添加一个属性FormField
。
<FormField
type="file"
ref={fileInputField}
onChange={handleNewFileUpload}
title=""
value=""
{...otherProps}
/>
与任何 DOM 事件(例如 )一样onClick
,处理该事件的函数将能够访问该事件对象。因此,该handleNewFileUpload
函数将以该事件对象作为其第一个参数。
const handleNewFileUpload = (e) => {
const { files: newFiles } = e.target;
if (newFiles.length) {
let updatedFiles = addNewFiles(newFiles);
setFiles(updatedFiles);
callUpdateFilesCb(updatedFiles);
}
};
在上面的函数中,我们从属性中访问用户选择的文件e.target.files
,然后将其传递给名为的函数addNewFiles
。然后,我们获取返回值addNewFiles
并将其传递给以setFiles
更新files
状态。由于状态的任何更改都files
必须发送到父组件,因此我们需要调用该callUpdateFilesCb
函数。
该addNewFiles
函数接受一个FileList对象(e.target.files
上面返回一个 FileList),对其进行迭代,并返回一个对象,其中键是文件名,值是 File 对象。
const addNewFiles = (newFiles) => {
for (let file of newFiles) {
if (file.size <= maxFileSizeInBytes) {
if (!otherProps.multiple) {
return { file };
}
files[file.name] = file;
}
}
return { ...files };
};
“如果没有multiple
属性,为什么要检查otherProps
?”
如前所述,我们使用otherProps
变量为文件输入标签添加属性。因此,如果我们不multiple
向文件上传组件传递 prop,则文件输入标签将无法选择多个文件。简而言之,如果传入multiple
prop,则所选文件将被添加到files
状态中。否则,选择新文件将删除先前的files
状态,并用新选择的文件替换它。
该callUpdateFilesCb
函数获取从 返回的值addNewFiles
,将files
状态转换为数组并调用该updateFilesCb
函数(从 props)。
“既然我们可以在函数内使用状态,为什么还要传递updatedFiles
给它呢?”callUpdateFilesCb
files
由于 React 状态更新是异步的,因此无法保证在callUpdateFilesCb
调用时files
状态会发生变化。
“为什么我们必须将files
状态转换为数组?”
我们不这么做!但是,当将处于files
状态的文件上传到某些第三方服务(例如 Firebase 云存储)时,使用数组会更容易。
const convertNestedObjectToArray = (nestedObj) =>
Object.keys(nestedObj).map((key) => nestedObj[key]);
const callUpdateFilesCb = (files) => {
const filesAsArray = convertNestedObjectToArray(files);
updateFilesCb(filesAsArray);
};
要删除文件,我们首先需要onClick
向组件添加一个属性RemoveFileIcon
。
<RemoveFileIcon
className="fas fa-trash-alt"
onClick={() => removeFile(fileName)}
/>
该removeFile
函数将获取一个文件名,将其从files
状态中删除,更新files
状态,并将更改通知父组件。
const removeFile = (fileName) => {
delete files[fileName];
setFiles({ ...files });
callUpdateFilesCb({ ...files });
};
这是具有上述所有功能的组件:
import React, { useRef, useState } from "react";
import {
FileUploadContainer,
FormField,
DragDropText,
UploadFileBtn,
FilePreviewContainer,
ImagePreview,
PreviewContainer,
PreviewList,
FileMetaData,
RemoveFileIcon,
InputLabel
} from "./file-upload.styles";
const KILO_BYTES_PER_BYTE = 1000;
const DEFAULT_MAX_FILE_SIZE_IN_BYTES = 500000;
const convertNestedObjectToArray = (nestedObj) =>
Object.keys(nestedObj).map((key) => nestedObj[key]);
const convertBytesToKB = (bytes) => Math.round(bytes / KILO_BYTES_PER_BYTE);
const FileUpload = ({
label,
updateFilesCb,
maxFileSizeInBytes = DEFAULT_MAX_FILE_SIZE_IN_BYTES,
...otherProps
}) => {
const fileInputField = useRef(null);
const [files, setFiles] = useState({});
const handleUploadBtnClick = () => {
fileInputField.current.click();
};
const addNewFiles = (newFiles) => {
for (let file of newFiles) {
if (file.size < maxFileSizeInBytes) {
if (!otherProps.multiple) {
return { file };
}
files[file.name] = file;
}
}
return { ...files };
};
const callUpdateFilesCb = (files) => {
const filesAsArray = convertNestedObjectToArray(files);
updateFilesCb(filesAsArray);
};
const handleNewFileUpload = (e) => {
const { files: newFiles } = e.target;
if (newFiles.length) {
let updatedFiles = addNewFiles(newFiles);
setFiles(updatedFiles);
callUpdateFilesCb(updatedFiles);
}
};
const removeFile = (fileName) => {
delete files[fileName];
setFiles({ ...files });
callUpdateFilesCb({ ...files });
};
return (
<>
<FileUploadContainer>
<InputLabel>{label}</InputLabel>
<DragDropText>Drag and drop your files anywhere or</DragDropText>
<UploadFileBtn type="button" onClick={handleUploadBtnClick}>
<i className="fas fa-file-upload" />
<span> Upload {otherProps.multiple ? "files" : "a file"}</span>
</UploadFileBtn>
<FormField
type="file"
ref={fileInputField}
onChange={handleNewFileUpload}
title=""
value=""
{...otherProps}
/>
</FileUploadContainer>
<FilePreviewContainer>
<span>To Upload</span>
<PreviewList>
{Object.keys(files).map((fileName, index) => {
let file = files[fileName];
let isImageFile = file.type.split("/")[0] === "image";
return (
<PreviewContainer key={fileName}>
<div>
{isImageFile && (
<ImagePreview
src={URL.createObjectURL(file)}
alt={`file preview ${index}`}
/>
)}
<FileMetaData isImageFile={isImageFile}>
<span>{file.name}</span>
<aside>
<span>{convertBytesToKB(file.size)} kb</span>
<RemoveFileIcon
className="fas fa-trash-alt"
onClick={() => removeFile(fileName)}
/>
</aside>
</FileMetaData>
</div>
</PreviewContainer>
);
})}
</PreviewList>
</FilePreviewContainer>
</>
);
};
export default FileUpload;
用例
现在让我们使用 App 组件中的文件上传组件来查看它的实际运行情况!
在该App.js
文件中,我们将创建一个简单的表单并添加状态来存储表单数据。
import React, { useState } from "react";
function App() {
const [newUserInfo, setNewUserInfo] = useState({
profileImages: []
});
const handleSubmit = (event) => {
event.preventDefault();
//logic to create a new user...
};
return (
<div>
<form onSubmit={handleSubmit}>
<button type="submit">Create New User</button>
</form>
</div>
);
}
export default App;
现在添加文件上传组件。
import React, { useState } from "react";
import FileUpload from "./components/file-upload/file-upload.component";
function App() {
const [newUserInfo, setNewUserInfo] = useState({
profileImages: []
});
const handleSubmit = (event) => {
event.preventDefault();
//logic to create a new user...
};
return (
<div>
<form onSubmit={handleSubmit}>
<FileUpload
accept=".jpg,.png,.jpeg"
label="Profile Image(s)"
multiple
/>
<button type="submit">Create New User</button>
</form>
</div>
);
}
export default App;
注意,我们还没有添加prop。在此之前,我们需要创建一个只更新 state属性的updateFilesCb
函数。profileImages
newUserInfo
const updateUploadedFiles = (files) =>
setNewUserInfo({ ...newUserInfo, profileImages: files });
我们现在将此函数作为updateFilesCb
prop 传递。因此,每当文件上传组件的状态发生变化时files
,它都会保存在状态profileImages
的属性中newUserInfo
。
import React, { useState } from "react";
import FileUpload from "./components/file-upload/file-upload.component";
function App() {
const [newUserInfo, setNewUserInfo] = useState({
profileImages: []
});
const updateUploadedFiles = (files) =>
setNewUserInfo({ ...newUserInfo, profileImages: files });
const handleSubmit = (event) => {
event.preventDefault();
//logic to create new user...
};
return (
<div>
<form onSubmit={handleSubmit}>
<FileUpload
accept=".jpg,.png,.jpeg"
label="Profile Image(s)"
multiple
updateFilesCb={updateUploadedFiles}
/>
<button type="submit">Create New User</button>
</form>
</div>
);
}
export default App;
“为什么我们要将accept
andmultiple
属性传递给文件上传组件?”
由于任何额外的道具都会传递给文件输入标签,因此文件输入标签将具有accept
andmultiple
属性。
该multiple
属性允许用户在文件资源管理器中选择多个文件。
该accept
属性可防止用户选择与指定文件类型不同的文件类型(在本例中为 jpg、png、jpeg)。
现在我们已经完成了,运行npm start
并访问 localhost:3000。应该会出现以下内容:
作为参考,可以在https://github.com/Chandra-Panta-Chhetri/react-file-upload-tutorial找到代码。