在React中上传图片之前获取预览图像

85

这里有很多类似的例子,但好像没有找到针对React的。我已经成功将纯JS转换为React,但是遇到了一个错误

答案看起来很简单,所以我在React中这样做:

getInitialState: function(){
  return{file: []}
},

_onChange: function(){
  // Assuming only image
  var file = this.refs.file.files[0];
  var reader = new FileReader();
  var url = reader.readAsDataURL(file);
  console.log(url) // Would see a path?
  // TODO: concat files for setState
},

render: function(){
 return(
  <div>
    <form>
      <input 
        ref="file" 
        type="file" 
        name="user[image]" 
        multiple="true"
        onChange={this._onChange}/>
     </form>
    {/* Only show first image, for now. */}
    <img src={this.state.file[0} />
  </div>
 )
};

基本上我看到的所有答案都展示了类似于我所拥有的东西。在React应用程序中有何差异?

关于答案:

enter image description here

16个回答

112

Cels的回答基础上进行扩展,并避免@El Anonimo警告的内存泄漏,我们可以使用useEffect来创建预览并在组件卸载后进行清理,代码如下:

useEffect(() => {
   // create the preview
   const objectUrl = URL.createObjectURL(selectedFile)
   setPreview(objectUrl)

   // free memory when ever this component is unmounted
   return () => URL.revokeObjectURL(objectUrl)
}, [selectedFile])

完整的代码可能长这样

export const ImageUpload = () => {
    const [selectedFile, setSelectedFile] = useState()
    const [preview, setPreview] = useState()

    // create a preview as a side effect, whenever selected file is changed
    useEffect(() => {
        if (!selectedFile) {
            setPreview(undefined)
            return
        }

        const objectUrl = URL.createObjectURL(selectedFile)
        setPreview(objectUrl)

        // free memory when ever this component is unmounted
        return () => URL.revokeObjectURL(objectUrl)
    }, [selectedFile])

    const onSelectFile = e => {
        if (!e.target.files || e.target.files.length === 0) {
            setSelectedFile(undefined)
            return
        }

        // I've kept this example simple by using the first image instead of multiple
        setSelectedFile(e.target.files[0])
    }

    return (
        <div>
            <input type='file' onChange={onSelectFile} />
            {selectedFile &&  <img src={preview} /> }
        </div>
    )
}

1
嘿,现在是2020年!不错! - Sylar
1
嘿@Jay Wick,URL.createObjectURL已经被弃用了吗?我看到了这个链接:https://developers.google.com/web/updates/2018/10/chrome-71-deps-rems#remove_urlcreateobjecturl_from_mediastream - Reine_Ran_
仍然适用于2021年11月。这让我免去了很多压力。谢谢朋友。 - Nzadibe
3
GG给Jay。2022年6月仍然完美运行。 - Loudrous
1
@Yehuda 如果您使用objectUrl调用回调属性,它不起作用吗?例如props.onPreviewChange(objectUrl)而不是setPreview(objectUrl)调用?当然,就像React一样,它将转到父组件,并且您必须以另一种方式在对等组件之间管理状态。 - Jay Wick
显示剩余4条评论

58

没有区别,只需要在load事件完成时读取您的图像。在load end事件处理程序之后,只需设置您的状态:

getInitialState: function(){
  return{file: []}
}

_onChange: function(){
  // Assuming only image
  var file = this.refs.file.files[0];
  var reader = new FileReader();
  var url = reader.readAsDataURL(file);

   reader.onloadend = function (e) {
      this.setState({
          imgSrc: [reader.result];
      })
    }.bind(this);
  console.log(url) // Would see a path?
  // TODO: concat files
},

render: function(){
 return(
  <div>
    <form>
      <input 
        ref="file" 
        type="file" 
        name="user[image]" 
        multiple="true"
        onChange={this_onChange}/>
     </form>
    {/* Only show first image, for now. */}
    <img src={this.state.imgSrc} />
  </div>
 )
}

谢谢。result在哈希中为空,但在外部不为空。有意义吗?reader显示哈希,但result为空。请参见帖子中的图片。 - Sylar
在该函数中可能为空,因为我们已经将其绑定到loadend函数,调试渲染函数并检查状态是否已更新。 - Piyush.kapoor
6
我发现了你代码中的错误。把reader.result;修改为[reader.result]即可。谢谢! - Sylar
Refs已经被弃用,现在必须先访问[current]对象再访问文件。 例如: this.textInput.current.focusTextInput() - Mohammed Metwally
1
这个回答很老了,但我想分享一下我的发现。 不需要做所有繁琐的文件初始化工作等,你可以这样做: const file = e.target.files[0]; if (!file) return; // 否则将会抛出解析错误 const url = URL.createObjectURL(file); url可以绑定到<img src={url} />中。希望对某些人有所帮助。 - Hasintha Abeykoon
显示剩余2条评论

20

请注意,这对我来说完美地起作用了。

state = {
  img: logo
}

handleChangeImage = e => {
  this.setState({[e.target.name]: URL.createObjectURL(e.target.files[0])})
}

<input type="file" id="img" name="img" accept="image/*" className="w-100" onChange={this.handleChangeImage}/>

<img src={this.state.img} alt="img"/>

查看以下链接以获取更多详细信息:https://medium.com/@650egor/react-30-day-challenge-day-2-image-upload-preview-2d534f8eaaa


哇,我在预览多张照片方面花了大约2个小时被FileReader()卡住了,而实际上URL.createObjectURL超级简单且在所有浏览器中都有支持。 - ronaldtgi

19

这里有一个简单而有效的解决方案,可以显示所有已选中的图片。

getInitialState: function(){
  return{file: []}
}

_onChange: (event)=>{
    this.setState({
        imgs: event.target.files
    })
},

render: function(){
 return(
  <div>
    <form>
      <input 
        ref="file" 
        type="file" 
        name="user[image]" 
        multiple="true"
        onChange={this._onChange}/>
    </form>

    {/* Display all selected images. */}        
    {this.state.imgs && [...this.state.imgs].map((file)=>(
       <img src={URL.createObjectURL(file)} />
    ))}

  </div>
 )
}

8
请记住,每次使用createObjectURL(file)创建 URL 时,结果 URL 都会留存在内存中。为了防止内存泄漏,请调用 URL.revokeObjectURL(<previously_created_objectUrl>) 将旧的图像 URL 从内存中删除。发现于 https://medium.com/@scyrizales/dont-forget-that-if-you-are-going-to-use-this-to-show-images-when-you-replace-the-image-with-a688b397725b - El Anonimo

7

这里有一个好的例子: https://www.kindacode.com/article/react-show-image-preview-before-uploading/

这篇文章介绍了如何在上传图片之前在React中显示图片预览。
const App = () => {
  const [selectedImage, setSelectedImage] = useState();

  const imageChange = (e) => {
    if (e.target.files && e.target.files.length > 0) {
      setSelectedImage(e.target.files[0]);
    }
  };

  const removeSelectedImage = () => {
    setSelectedImage();
  };

  return (
    <>
      <div>
        <input
          accept="image/*"
          type="file"
          onChange={imageChange}
        />

        {selectedImage && (
          <div>
            <img
              src={URL.createObjectURL(selectedImage)}
              alt="Thumb"
            />
            <button onClick={removeSelectedImage}>
              Remove This Image
            </button>
          </div>
        )}
      </div>
    </>
  );
};

2
尝试这个多文件上传和预览功能:
import React,{useState} from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import PhotoCamera from '@material-ui/icons/PhotoCamera';
 

const useStyles = makeStyles((theme) => ({
  root: {
    '& > *': {
      margin: theme.spacing(1),
    },
  },
  input: {
    display: 'none',
  },
}));

export default function UploadButtons() {
  const classes = useStyles();
  const [files,setFile]=useState([]);
  const handlerFile=(e)=>{    
    console.log(e.target.files);
    
    let allfiles=[]
   for(let i=0;i<e.target.files.length;i++){
    allfiles.push(e.target.files[i]);
   }
    if(allfiles.length>0){
      setFile(allfiles);  
    }
  };
  return (
    <div className={classes.root}>
        {files.map((file, key) => {
                            return (
                                <div key={key} className="Row">
                                    <span className="Filename">
                                        {file.name}
                                        <img src={URL.createObjectURL(file)}  alt={file.name} />
                                    </span>
                                </div>
                            )
                        })}
      <input
        accept="image/*"
        className={classes.input}
        id="contained-button-file"
        multiple
        type="file"
        onChange={handlerFile}
      />
      <label htmlFor="contained-button-file">
        <Button variant="contained" color="primary" component="span">
          Upload
        </Button>
      </label> 
      
  );
}

2

我曾经花费很大的力气才解决了多文件上传的问题,所以如果其他人也遇到了这个问题,这里有一个解决方案:

最初的回答:

const onFileChange = e => {
e.preventDefault();
const filesList = e.target.files;

if (filesList.length === 0) return;
//Spread array to current state preview URLs
let arr = [...data.imagePreviewUrls];
for (let i = 0; i < filesList.length; i++) {
  const file = filesList[i];

  const reader = new FileReader();
  reader.onload = upload => {
    //push new image to the end of arr
    arr.push(upload.target.result);
    //Set state to arr
    setData({
      ...data,
      imagePreviewUrls: arr
    });
  };
  reader.readAsDataURL(file);
}
};

基本上,在第一个 onload 事件触发之前,for 循环就已经解决了,每次迭代都会重置状态。要解决这个问题,只需在 for 循环外创建一个数组,并在 onload 中将新项推入该数组。这将处理多个文件,即使用户关闭文件对话框并稍后选择上传更多文件,只要状态没有被重置。"最初的回答"

1
 1. image with preview and close button

import React,{useState} from "react";

export default function App() {
  const  [preview,setPreview] = useState([]);

  const maxLength = 3;



  const onSelectFile=(e)=>{
      for (var i=0;i < e.target.files.length;i++){
        arrayofFiles.push(e.target.files[i]);
        
    }
    
  let images = [];
  arrayofFiles.map((e)=>{
  const ImageUrl =  URL.createObjectURL(e);
  images.push(ImageUrl)
      
  })
  setPreview(images)
  }



  const removeImageFromArray=(e)=>{
    const index = e.target.id;
    let newPreview = [...preview];

    newPreview.splice(index,1);
    setPreview(newPreview);
  }
  return (
    <div className="App">
      <h1>Image view and close</h1>
      <input type="file" name="file" multiple onChange={onSelectFile}/>
    {preview.map((img,index)=>
      (
        <div key={index}>
         <img src={img} id={index} alt="pic1" width="250" height="250" />
           <button
              id={index}
              key={index}
              onClick={(e) => {
                removeImageFromArray(e);
              }}
            >
              Close
            </button>

        </div>
      )  
    )}
     </div>
  );
}

1

试一试

import React,{useState} from 'react';

export default function App() {
  const [photo123, setphoto123] = useState(null);
  const handleInputChange = (event) => {
    setphoto123(URL.createObjectURL(event.target.files[0]));
  }

  return (
    <div style={{margin:50}}>
      <input type="file" className="form-control"
          onChange={handleInputChange} />
      <img  src={photo123} alt="Image_Photo" />
    </div>
  );
}


1
这是我的解决方案。非常简单易用。 JSX
<form onSubmit={this.onSubmit} >      
      <input
         ref="uploadImg"
         type="file"
         name="selectedFile"
         onChange={this._onChange}
        />
</form>

<img src={this.state.imageUrl} />

和函数

  _onChange = (e) => {

    const file    = this.refs.uploadImg.files[0]
    const reader  = new FileReader();

    reader.onloadend = () => {
        this.setState({
            imageUrl: reader.result
        })
    }
    if (file) {
        reader.readAsDataURL(file);
        this.setState({
            imageUrl :reader.result
        })
    } 
    else {
        this.setState({
            imageUrl: ""
        })
    }
}

当然,您需要像imageUrl这样的状态,它是JSX中的src。


请使用ObjectURL代替base64。 - Endless

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接