λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ‘¨‍πŸ’» web.dev/fe

File API λ₯Ό μ΄μš©ν•œ Input μ»΄ν¬λ„ŒνŠΈ λ§Œλ“€κΈ°

by HandHand 2022. 8. 13.

 

 

πŸ“Œ λ“€μ–΄κ°€λ©°

ν”„λ‘œμ νŠΈ 도쀑 HTML Input 을 λž˜ν•‘ν•œ μ»΄ν¬λ„ŒνŠΈλ₯Ό ν†΅ν•΄μ„œ

JavaScript 의 File 객체λ₯Ό 닀뀄볼 κΈ°νšŒκ°€ μƒκ²ΌμŠ΅λ‹ˆλ‹€.

File API 에 λŒ€ν•œ λͺ¨λ“  것을 이번 ν¬μŠ€νŠΈμ—μ„œ λ‹€λ£¨μ§€λŠ” μ•Šμ§€λ§Œ,

ν•„μš”ν•œ κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œ μ•Œμ•„λ³Έ λ‚΄μš©λ“€μ„ μ •λ¦¬ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

πŸ“Œ File API λž€?

File API λŠ” νŒŒμΌμ— λŒ€ν•œ 정보λ₯Ό μ œκ³΅ν•˜κ³  JavaScript λ₯Ό μ΄μš©ν•΄μ„œ

νŒŒμΌκ°μ²΄μ— μ ‘κ·Όν•  수 μžˆλŠ” λ‹€μ–‘ν•œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.

μ—…λ‘œλ“œ ν•œ 파일의 이름을 λ³€κ²½ν•˜κ³  μ‚­μ œν•˜λŠ” μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“€μ–΄λ³΄λ©΄μ„œ

File API 의 ν™œμš© 방법에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

λ¨Όμ € νŒŒμΌμ„ λ‹€λ£¨λŠ” FileInput μ»΄ν¬λ„ŒνŠΈλ₯Ό λ‹€μŒκ³Ό 같이 μ •μ˜ν•˜κ² μŠ΅λ‹ˆλ‹€.

μ—¬λŸ¬κ°œμ˜ νŒŒμΌμ„ μ—…λ‘œλ“œ ν•  수 μžˆλ„λ‘ ν•˜κΈ° μœ„ν•΄ multiple 속성을 μΆ”κ°€ν•©λ‹ˆλ‹€.

 

import React from 'react'

export default function FileInput() {
  return <input type="file" multiple />
}

 

그리고 μ΅œμƒμœ„ App μ»΄ν¬λ„ŒνŠΈμ—μ„œ 이λ₯Ό λΆˆλŸ¬μ™€ 화면에 κ·Έλ €μ€λ‹ˆλ‹€.

 

import React from 'react';
import Input from './Input'

export default function App() {
  return (
    <div>
      <Input />
    </div>
  );
}

 

이제 μ—¬λŸ¬κ°œμ˜ νŒŒμΌμ„ λ™μ‹œμ— μ—…λ‘œλ“œν•˜λŠ” input μ»΄ν¬λ„ŒνŠΈκ°€ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€!

 

기본적인 Input μ»΄ν¬λ„ŒνŠΈκ°€ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€

 

πŸ“Œ μ—…λ‘œλ“œ ν•œ νŒŒμΌμ„ 리슀트 ν˜•νƒœλ‘œ μ‹œκ°ν™”ν•˜κΈ°

μš°λ¦¬κ°€ μ›ν•˜λŠ”κ±΄ μ—…λ‘œλ“œ ν•œ 각각의 νŒŒμΌλ“€μ˜ 이름을 λ³€κ²½ν•˜κ±°λ‚˜ μ‚­μ œν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

이λ₯Ό μœ„ν•΄μ„œ μ—…λ‘œλ“œ 된 νŒŒμΌλ“€μ„ 리슀트 ν˜•νƒœλ‘œ μ‹œκ°ν™”ν•˜λŠ” μž‘μ—…μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

λ¨Όμ € FileInput 에 파일 객체λ₯Ό κ΄€λ¦¬ν•˜λŠ” μƒνƒœκ°’μ„ μΆ”κ°€ν•©λ‹ˆλ‹€.

 

파일 κ°μ²΄λŠ” File μΈν„°νŽ˜μ΄μŠ€λ‘œ ν‘œν˜„λ˜λ©° μ΄λŠ” Blob 을 기반으둜 κ΅¬ν˜„λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€.

그리고 File μΈν„°νŽ˜μ΄μŠ€μ—λŠ” name, lastModified λ“± λ‹€μ–‘ν•œ readonly 속성이 μ‘΄μž¬ν•˜μ—¬

νŒŒμΌμ— λŒ€ν•œ λ‹€μ–‘ν•œ 메타 정보듀을 κ°€μ Έμ˜¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ˜ˆμ‹œλ₯Ό κ°„λ‹¨ν•˜κ²Œ ν•˜κΈ° μœ„ν•΄μ„œ, 파일 이름과 μ‚­μ œ 및 이름 λ³€κ²½ λ²„νŠΌλ§Œ μΆ”κ°€ν•˜λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

 

import React, { useState } from "react";

export default function FileInput() {
  const [files, setFiles] = useState<File[]>([]);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFiles(Array.from(e.target.files || []));
  };

  const handleDelete = () => {
    /** TODO */
  };
  const handleRename = () => {
    /** TODO */
  };

  return (
    <div>
      <input type="file" multiple onChange={handleChange} />
      <ul>
        {files.map((file) => (
          <li key={file.name}>
            {file.name}
            <button onClick={handleDelete}>μ‚­μ œ</button>
            <button onClick={handleRename}>이름변경</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

 

파일의 μ‚­μ œμ™€ 이름 λ³€κ²½ λ²„νŠΌμ΄ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€!

 

πŸ“Œ 파일 μ‚­μ œν•˜κΈ°

handleDelete κ΅¬ν˜„ν•˜κΈ°

λ¨Όμ € νŒŒμΌμ„ μ‚­μ œν•˜λŠ” ν•Έλ“€λŸ¬ (handleDelete) λΆ€ν„° κ΅¬ν˜„ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

μ„ νƒλœ μΈλ±μŠ€κ°’μ„ μ „λ‹¬ν•΄μ„œ slice 연산을 톡해 μƒˆλ‘œμš΄ 파일 리슀트λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€.

 

import React, { useState } from "react";

export default function FileInput() {
  const [files, setFiles] = useState<File[]>([]);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFiles(Array.from(e.target.files || []));
  };

  const handleDelete = (index: number) => {
    setFiles([...files.slice(0, index), ...files.slice(index + 1)]);
  };

  const handleRename = () => {
    /** TODO */
  };

  return (
    <div>
      <input type="file" multiple onChange={handleChange} />
      <ul>
        {files.map((file, index) => (
          <li key={`${file.name}_${index}`}>
            {file.name}
            <button onClick={() => handleDelete(index)}>μ‚­μ œ</button>
            <button onClick={handleRename}>이름변경</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

 

이제 μœ„ μ½”λ“œλ₯Ό μ‹€μ œλ‘œ μ‹€ν–‰μ‹œμΌœλ³΄λ©΄ λ‹€μŒκ³Ό 같이 μ‚­μ œ λ™μž‘μ΄ μ΄λ£¨μ–΄μ§€λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ‚­μ œλŠ” λ˜λŠ”λ°.. λ­”κ°€ μ΄μƒν•˜λ‹€

 

input.files 와 뢈일치..? πŸ€”

그런데 μ’€ μ΄μƒν•œ 뢀뢄이 μžˆμ§€ μ•Šμ€κ°€μš”?

File 객체λ₯Ό λ‹΄κ³  μžˆλŠ” μƒνƒœκ°’ (files) λŠ” λ³€ν™”κ°€ λ°˜μ˜λ˜μ§€λ§Œ,

μ‹€μ œ input μš”μ†Œμ˜ files μ—λŠ” 이 λ³€ν™”κ°€ λ°˜μ˜λ˜μ§€ μ•Šμ•„μ„œ

files 에 ν˜„μž¬ 3개의 파일 λͺ©λ‘μ΄ μœ μ§€λ˜λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

 

File 객체듀이 κ·ΈλŒ€λ‘œ μžˆλ‹€..πŸ€”

 

그러면 이 문제λ₯Ό μ–΄λ–»κ²Œ ν•΄κ²°ν•  수 μžˆμ„κΉŒμš”?

μ΄λŠ” μƒˆλ‘œμš΄ FileList λ₯Ό fileInput.files 에 ν• λ‹Ήν•¨μœΌλ‘œμ¨ κ°€λŠ₯ν•©λ‹ˆλ‹€.

FileList μžμ²΄λŠ” μƒμ„±μž ν•¨μˆ˜κ°€ μ—†κΈ° λ•Œλ¬Έμ— 직접 μƒμ„±ν•˜λŠ” 것은 λΆˆκ°€λŠ₯ ν•©λ‹ˆλ‹€.

λŒ€μ‹  DataTransfer μ—μ„œλ„ FileList λ₯Ό μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— 이λ₯Ό ν†΅ν•΄μ„œ 생성 κ°€λŠ₯ν•©λ‹ˆλ‹€.

 

DataTransfer μΈν„°νŽ˜μ΄μŠ€ λͺ…μ„Έ

DataTransfer λŠ” drag & drop λ™μž‘ 쀑 λ“œλž˜κ·Έ 쀑인 μƒνƒœμ˜ 데이터λ₯Ό μ €μž₯ν•˜κΈ° μœ„ν•œ μΈν„°νŽ˜μ΄μŠ€μž…λ‹ˆλ‹€.

ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€κ°€ μ œκ³΅ν•˜λŠ” ν”„λ‘œνΌν‹°λ“€μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

 

interface DataTransfer {
  attribute DOMString dropEffect;
  attribute DOMString effectAllowed;

  readonly attribute DataTransferItemList items; // βœ…
    readonly attribute FileList files; // βœ…

  undefined setDragImage(Element image, long x, long y);

  DOMString getData(DOMString format);
  undefined setData(DOMString format, DOMString data);
  undefined clearData(optional DOMString format);

};

 

μ—¬κΈ°μ„œ μ΄λ²ˆμ— μ‚¬μš©ν•  것은 items 와 files μž…λ‹ˆλ‹€.

items λŠ” DataTransferItemList μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ”°λ₯΄λ©°

ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€μ—μ„œλŠ” λ‹€μŒκ³Ό 같은 λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.

 

items.remove(index)
-> drag data store μ—μ„œ index λ²ˆμ§Έμ— ν•΄λ‹Ήν•˜λŠ” 데이터λ₯Ό μ‚­μ œν•©λ‹ˆλ‹€.

items.clear()
-> drag data store μ—μ„œ λͺ¨λ“  데이터λ₯Ό μ‚­μ œν•©λ‹ˆλ‹€. 

items.add(data)
items.add(data, type)
-> drag data store 에 μƒˆλ‘œμš΄ 데이터λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.

 

DataTransfer λ₯Ό ν™œμš©ν•΄μ„œ 파일 μ‚­μ œ κ΅¬ν˜„ν•˜κΈ°

이제 μœ„μ—μ„œ μ‚΄νŽ΄λ³Έ λ‚΄μš©μ„ λ°”νƒ•μœΌλ‘œ μ‹€μ œλ‘œ 파일 μ‚­μ œλ₯Ό κ΅¬ν˜„ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

λ¨Όμ € input νƒœκ·Έμ— μ ‘κ·Όν•˜κΈ° μœ„ν•œ ref 객체λ₯Ό μ„ μ–Έν•΄μ€λ‹ˆλ‹€.

 

import React, { useState, useRef } from "react";

export default function FileInput() {
  const inputRef = useRef<HTMLInputElement>(null); // βœ… input tag 접근을 μœ„ν•œ ref

    /** no diff */

  return (
    <div>
      <input ref={inputRef} type="file" multiple onChange={handleChange} />
            /** no diff */
        </div>
    )
}

 

이후 FileList λ₯Ό μ €μž₯ν•  DataTransfer 객체λ₯Ό μƒμ„±ν•˜κ³ ,

μ‚­μ œν•  index λ₯Ό μ œμ™Έν•œ λ‚˜λ¨Έμ§€ File 듀을 drag data store 에 μΆ”κ°€ν•΄μ€λ‹ˆλ‹€.

그리고 μƒˆλ‘œμš΄ FileList 둜 input.files λ₯Ό κ΅μ²΄ν•©λ‹ˆλ‹€.

 

const handleDelete = (index: number) => {
  const newFiles = [...files.slice(0, index), ...files.slice(index + 1)];

  const store = new DataTransfer();
  newFiles.forEach((file) => store.items.add(file));

  if (inputRef.current) {
    inputRef.current.files = store.files; // βœ… μƒˆλ‘œμš΄ FileList 둜 κ΅μ²΄ν•©λ‹ˆλ‹€.
  }

  setFiles(newFiles);
};

 

이제 μ‚­μ œν• λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ FileList κ°€ μƒμ„±λ˜μ–΄ input.files 도 ν•¨κ»˜ μ‚­μ œμ²˜λ¦¬κ°€ λ©λ‹ˆλ‹€!

 

input.files 도 ν•¨κ»˜ μ‚­μ œλœλ‹€.

 

πŸ“Œ 파일 이름 λ³€κ²½ν•˜κΈ°

νŒŒμΌμ„ μ‚­μ œν• λ•Œμ™€ λ§ˆμ°¬κ°€μ§€λ‘œ 파일 이름을 λ³€κ²½ν•˜λŠ” ν•Έλ“€λŸ¬ (handleRename) μ—­μ‹œ

μƒˆλ‘œμš΄ FileList λ₯Ό ν• λ‹Ήν•˜λŠ” 방법을 μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

λ‹€λ§Œ μ•žμ„œ μ‚΄νŽ΄λ³ΈλŒ€λ‘œ File 객체의 name 속성은 μ½κΈ°μ „μš©μ΄κΈ° λ•Œλ¬Έμ—,

λ‹€μŒκ³Ό 같이 직접 접근을 톡해 이름을 λ³€κ²½ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

 

files[0].name = 'new name' // ❌

 

λ”°λΌμ„œ 이 λŒ€μ‹ μ— λ³€κ²½ν•  파일 μ΄λ¦„μœΌλ‘œ μƒˆλ‘œμš΄ File 객체λ₯Ό λ§Œλ“œλŠ” 방법을 μ‚¬μš©ν•©λ‹ˆλ‹€.

 

File 객체 μƒμ„±ν•˜κΈ°

File κ°μ²΄λŠ” File μƒμ„±μž ν•¨μˆ˜ λ₯Ό μ΄μš©ν•΄μ„œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

 

const newFile = new File([oldFile], 'new_name.png', {type: oldFile.type});

 

λ§Œμ•½ IE 와 같이 File API λ₯Ό μ™„λ²½ν•˜κ²Œ μ§€μ›ν•˜μ§€ μ•ŠλŠ” ν™˜κ²½μ΄λΌλ©΄

File 의 κΈ°μ € 클래슀인 Blob 을 μ΄μš©ν•˜λŠ” 방법이 μžˆμŠ΅λ‹ˆλ‹€.

 

File ν΄λž˜μŠ€λŠ” κ²°κ΅­ Blob 을 ν™•μž₯ν•œ μΈν„°νŽ˜μ΄μŠ€μ΄κΈ° λ•Œλ¬Έμ— λ‹€μŒκ³Ό 같이

Blob μ—λŠ” μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” name κ³Ό lastModified 속성을 μΆ”κ°€ν•΄μ€ŒμœΌλ‘œμ¨

File 객체와 λ™μΌν•˜κ²Œ λ‹€λ£° 수 μžˆμŠ΅λ‹ˆλ‹€.

 

function createFileObject(
  bits: Blob[],
  name: string,
  options?: BlobPropertyBag,
) {
  try {
    return new File(bits, name, options)
  } catch (err) {
    /** IE λŠ” File API λ₯Ό μ§€μ›ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— Blob 을 μ‚¬μš©ν•©λ‹ˆλ‹€. */
    const blob: any = new Blob(bits, options || {})
    blob.lastModified = Date.now()
    blob.name = name
    return blob as File
  }
}

handleRename κ΅¬ν˜„ν•˜κΈ°

이제 μœ„ ν•¨μˆ˜λ₯Ό μ΄μš©ν•΄μ„œ 파일 이름을 λ³€κ²½ν•˜λŠ” ν•Έλ“€λŸ¬λ₯Ό κ΅¬ν˜„ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

본래 λͺ©μ μ— μ§‘μ€‘ν•˜κΈ° μœ„ν•΄μ„œ 파일 이름을 μ‚¬μš©μžλ‘œλΆ€ν„° μž…λ ₯ λ°›λŠ” 것이 μ•„λ‹ˆλΌ

λžœλ€ν•œ μ΄λ¦„μœΌλ‘œ μƒμ„±λ˜λ„λ‘ λ‹¨μˆœν™”ν•˜μ—¬ μ§„ν–‰ν•˜κ² μŠ΅λ‹ˆλ‹€. πŸ˜„

λ¨Όμ € λžœλ€ν•œ λ‚œμˆ˜λ₯Ό 톡해 μƒˆλ‘œμš΄ 파일 이름을 λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•©λ‹ˆλ‹€.

 

function createRandomFilename() {
  return `file_${crypto.getRandomValues(new Uint32Array(1))}`;
}

 

이제 μƒˆλ‘œμš΄ File 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ (createFileObject) λ₯Ό ν•¨κ»˜ μ΄μš©ν•΄μ„œ

파일 이름을 λ³€κ²½ν•˜λŠ” ν•Έλ“€λŸ¬ (handleRename) 을 κ΅¬ν˜„ν•˜κ² μŠ΅λ‹ˆλ‹€.

 

const handleRename = (index: number) => {
  const originFile = files[index];
  const newFilename = createRandomFilename();
  const newFileObject = createFileObject([originFile], newFilename, {
    type: originFile.type
  });

  const store = new DataTransfer();
  const copy = [...files];
  copy[index] = newFileObject;
  copy.forEach((file) => store.items.add(file));

  if (inputRef.current) {
    inputRef.current.files = store.files;
  }

  setFiles(copy);
};

 

index 에 ν•΄λ‹Ήν•˜λŠ” νŒŒμΌμ„ μƒˆλ‘œμš΄ 파일 객체둜 λ°”κΎΈμ–΄

파일 μ‚­μ œμ™€ λ™μΌν•˜κ²Œ DataTransfer λ₯Ό μ΄μš©ν•΄ μƒˆλ‘œμš΄ FileList λ₯Ό ν• λ‹Ήν•©λ‹ˆλ‹€.

 

μ™„μ„±!

 

πŸ“Œ CodeSandbox μ—μ„œ ν™•μΈν•˜κΈ°

μœ„μ—μ„œ κ΅¬ν˜„ν•œ μ»΄ν¬λ„ŒνŠΈλŠ” λ‹€μŒ λ§ν¬μ—μ„œ 확인해보싀 수 μžˆμŠ΅λ‹ˆλ‹€. πŸ˜„

 

 

 

πŸ“Œ 참고자료

File() - Web API | MDN

FileList - Web APIs | MDN

HTML

HTML

Rename A File With JavaScript In The Browser

Renaming a File() object in JavaScript

File Constructor support in Internet Explorer - wiliammbr's blog

Sending Emails Using Curl - The Right Way - Newdevzone

λ°˜μ‘ν˜•

πŸ’¬ λŒ“κΈ€