Получите предварительный просмотр изображения перед загрузкой в ​​React

Много примеров этого здесь, но не могу найти ни одного для реакции. Мне удалось преобразовать vanilla js, чтобы он реагировал, но получил ошибку.

Ответ выглядит достаточно простым, поэтому здесь я реагирую:

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

Ответ 1

Нет разницы, просто прочитайте свое изображение, когда закончится событие 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>
 )
}

Ответ 2

Вот простое и рабочее решение, которое отображает все выбранные изображения

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>
 )
}

Ответ 3

Расширяя ответ Cels и избегая утечек памяти с помощью createObjectURL, когда @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>
    )
}

Ответ 4

Есть мое решение. Очень простой и простой в использовании.

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, которое является нашим src в jsx.

Ответ 5

Я безумно пытался заставить это работать с несколькими файлами, так что, если у кого-то еще возникла эта проблема, вот вам:

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);
}
};

По сути, цикл for разрешается до того, как сработает даже первое событие onload, сбрасывая состояние при каждой итерации. Чтобы решить эту проблему, просто создайте массив вне цикла for и загрузите новый элемент в него. Это будет обрабатывать несколько файлов, даже если пользователь закроет диалоговое окно файла, а затем решит загрузить больше, если состояние не было сброшено.