input
태그의 속성 중 type="file"
을 이용하면 파일을 업로드 할 수 있다. (참고: type
에는 file 말고도 체크박스, 이메일, 이미지, 비밀번호 등등등 많은 유형이 있다.)
<input>
태그를 이용해 유저가 파일을 선택하면 FileList
객체가 반환된다.
drag & drop
에서 DataTransfer
객체가 반환된다.
HTMLCanvasElement
의 mozGetAsFile()
API에서도 얻을 수도 있다. MDN
<canvas>
가 포함하고 있는 이미지를 나타내는 File 객체를 반환합니다. 이 파일은 메모리 기반의 파일이며, name의 이름을 갖습니다. 만약 type이 지정되지 않는다면, 이미지는 기본적으로 image/png의 타입을 갖게 될 것입니다.input 태그의 type을 file로 주면 위와 같이 버튼이 생기고, 파일을 선택할 수 있다. JS 코드를 쓰지 않아도 업로드는 되지만, 이렇게만 두면 파일을 업로드하는 의미가 없다. 😅 파일 업로드 후에 다양한 일을 할 수 있도록 이벤트리스너를 달아서 핸들링해야 한다. 파일에 관한 이벤트 핸들러가 FileReader
라는 클래스에 있기 때문에, 우리는 FileReader
의 인스턴스를 만들어서 이벤트를 핸들링 해야 한다.
const fileEvent = (e) => {
const reader = new FileReader();
reader.onload = () => {
console.log('파일 업로드 완료.');
};
reader.readAsText(e.target.files[0]);
}
$fileItem.addEventListener('change', fileEvent);
// $fileItem : querySelector로 불러온 input 버튼
FileReader
의 인스턴스 reader
를 만든다.
load 이벤트 핸들러인 reader.onload
를 정의해준다. load 이벤트는 읽기 동작이 성공적으로 완료 되었을 때마다 발생한다.
load 이벤트를 발생시키기 위해서는 '읽기 동작'이 필요하므로 readAsText
를 실행한다. 업로드 된 파일은 e.target.files
에 저장이 되는데, 파일을 하나만 업로드했기 때문에 첫 번째 요소를 넣은 것이다.
FileReader의 상태는 세 가지가 있다.
EMPTY
: FileReader가 생성됐지만 아직 readAs
메서드가 불려지지 않은 상태
LOADING
: readAs
메서드가 실행된 상태. File 또는 Blob이 읽어지고 있고, 에러가 아직 발생하지 않은 상태
DONE
: read operation이 완료된 상태. 그 말인 즉슨, 다음 세 가지 상황 중 하나를 뜻한다.
3-1. File이나 Blob이 모두 읽어져서 메모리에 들어갔을 때
3-2. 발생한 에러를 읽었을 때
3-3. abort()
가 호출되어 읽기 작업이 취소됐을 때
다시 본문으로 돌아와서 코드를 보자.
const fileEvent = (e) => {
const reader = new FileReader();
reader.onload = () => { // 1)
console.log('파일 업로드 완료.');
};
reader.readAsText(e.target.files[0]); // 2)
}
$fileItem.addEventListener('change', fileEvent);
// $fileItem : querySelector로 불러온 input 버튼
위 코드에서 1)과 2)의 순서를 바꿔도, 즉 readAsText
를 onload
위로 올려도 정상적으로 동작한다. 오잉? 이벤트 핸들러가 등록되지 않았는데 어떻게 가능한 일이지? 일단 순서를 바꾼 코드는 아래와 같다.
const fileEvent = (e) => {
const reader = new FileReader();
reader.readAsText(e.target.files[0]);
reader.onload = () => {
console.log('파일 업로드 완료.');
};
}
$fileItem.addEventListener('change', fileEvent);
// $fileItem : querySelector로 불러온 input 버튼
readAsText
가 실행되면서 상태가 LOADING
으로 바뀌었고, 바로 다음 줄의 onload
코드가 실행되면서 이벤트 핸들러가 등록이 되었고, 그 후에 상태가 DONE
으로 바뀌어 콘솔 창에 '파일 업로드 완료'
라고 찍히는 것으로 보인다.
위 코드에서 onload
에 setTimeout
으로 지연을 시키니, 콘솔 창에 메시지가 나오지 않았다.
const fileEvent = (e) => {
const reader = new FileReader();
console.dir(e);
reader.readAsText(e.target.files[0]);
window.setTimeout(() => {
reader.onload = () => {
console.log('파일 업로드 완료.');
};
}, 2000);
}
$fileItem.addEventListener('change', fileEvent);
reader
의 상태가 DONE
으로 바뀌는 그 순간에 등록되어 있는 onload
가 없기 때문이다.
즉, readAsText
는 비동기적으로 실행되는 메서드이고, 다른 코드의 실행을 방해하지 않는 non-block이기 때문에 코드 순서를 바꿔도 동작하는 것이었다. 하지만 setTimeout
을 넣었을 때처럼 의도치 않은 비동기 함수가 있을 수 있기 때문에, 코드의 순서가 굉장히 중요해 보인다.
어텀도 엄청 자세하게 공부하시네요... 좀 보고, 잘 보고 갈게용 ㅎㅎㅎ