具有拖放功能的响应式 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.jsindex.jsindex.css
文件上传组件
安装依赖项
我们需要的依赖项是:
- 用于设置组件的样式
- 样式组件允许样式封装并通过 props 创建动态样式
- 用于编译样式组件中使用的 Sass 样式(可选,可以使用 CSS)
要安装它们,请运行npm i styled-components node-sass。
文件夹结构
构建文件夹和文件的一个好习惯是创建一个 components 文件夹,每个组件都对应一个文件夹。这样可以更轻松地找到每个组件的逻辑和样式。
按照此约定,在文件夹中创建一个组件文件夹src,然后在文件夹中创建一个文件上传文件夹components。
最后,在文件上传文件夹中,创建 2 个新文件。
file-upload.component.jsxfile-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 从父组件向文件输入标签添加属性。labelupdateFilesCbmaxFileSizeInBytesotherProps
“为什么我们要将标题和值属性设置为""?”
设置标题属性可以""删除鼠标悬停在输入标签上时默认显示的文本(“未选择文件”)。
将 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.createObjectURLcreateObjectURL 函数。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,则文件输入标签将无法选择多个文件。简而言之,如果传入multipleprop,则所选文件将被添加到files状态中。否则,选择新文件将删除先前的files状态,并用新选择的文件替换它。
该callUpdateFilesCb函数获取从 返回的值addNewFiles,将files状态转换为数组并调用该updateFilesCb函数(从 props)。
“既然我们可以在函数内使用状态,为什么还要传递updatedFiles给它呢?”callUpdateFilesCbfiles
由于 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函数。profileImagesnewUserInfo
const updateUploadedFiles = (files) =>
setNewUserInfo({ ...newUserInfo, profileImages: files });
我们现在将此函数作为updateFilesCbprop 传递。因此,每当文件上传组件的状态发生变化时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;
“为什么我们要将acceptandmultiple属性传递给文件上传组件?”
由于任何额外的道具都会传递给文件输入标签,因此文件输入标签将具有acceptandmultiple属性。
该multiple属性允许用户在文件资源管理器中选择多个文件。
该accept属性可防止用户选择与指定文件类型不同的文件类型(在本例中为 jpg、png、jpeg)。
现在我们已经完成了,运行npm start并访问 localhost:3000。应该会出现以下内容:
作为参考,可以在https://github.com/Chandra-Panta-Chhetri/react-file-upload-tutorial找到代码。
后端开发教程 - Java、Spring Boot 实战 - msg200.com


