📌 URL.createObjectURL()
: Blob 객체나 File 객체를 사용하여 URL을 생성할 수 있게 해주는 메서드
- 자바스크립트의 웹 API 중 하나
- 브라우저에서 동적으로 생성한 파일 데이터를 참조할 필요가 있을 때 사용한다.
- 특징
- 일회성 URL
: URL.createObjectURL()로 생성된 URL은 페이지가 열려 있는 동안만 유효하다.
브라우저가 해당 페이지를 새로 고침하거나 닫으면 더 이상 사용할 수 없다. - 메모리 사용
: 생성된 URL은 메모리를 사용하므로, 사용이 끝난 후 URL.revokeObjectURL() 메서드를 호출하여 URL을 해제해야 한다. - 브라우저 호환성
: 거의 모든 현대적인 브라우저에서 지원된다.
- 일회성 URL
- 사용 상황
- 파일 업로드: 사용자가 업로드한 파일을 미리보기할 때 사용
- 동적 이미지 생성: JavaScript로 생성한 이미지를 웹 페이지에 표시할 때 사용
- 파일 다운로드: 클라이언트 사이드에서 생성된 데이터를 파일로 다운로드할 때 사용
const objectURL = URL.createObjectURL(object);
- 매개변수
- object
: 객체 URL을 생성할 File, Blob, MediaSource 객체
- object
⏩ Blob 객체(Binary Large Object)
: 대량의 이진 데이터(바이너리 데이터)를 표현하는 객체
- 이미지, 비디오, 텍스트 파일 등 다양한 형태의 데이터를 포함할 수 있다.
- 이진 데이터로 텍스트, 이미지, 비디오 등 다양한 형식을 저장할 수 있다.
- 파일 시스템 API와 함께 사용되며, 파일 데이터를 메모리 내에서 처리할 수 있다.
⏩ File 객체
: Blob 객체의 서브클래스
- 브라우저에서 파일을 업로드할 때 사용한다.
사용자가 파일 입력 필드에서 파일을 선택하거나 드래그 앤 드롭할 때 이 객체를 사용 - 사용자가 선택한 파일에 대한 메타데이터(파일 이름, 파일 크기 등)를 포함하며, Blob의 기능을 제공한다.
⏩ Object URL
: URL.createObjectURL() 메서드를 사용하여 생성된 URL
- Blob 객체나 File 객체를 참조한다.
- 브라우저에서 해당 데이터에 접근할 수 있도록 해준다.
- 생성된 페이지가 열려 있는 동안만 유효다.
페이지가 새로 고쳐지거나 닫히면 URL은 더 이상 유효하지 않다. - 사용이 끝난 후 URL.revokeObjectURL() 메서드를 호출하여 Object URL을 해제하고 메모리를 정리해야 한다.
📌 URL.revokeObjectURL()
: 브라우저에서 생성된 Object URL을 해제하는 데 사용되는 메서드
- Object URL
: URL.createObjectURL()을 통해 생성된 URL
➡︎ 브라우저에서 Blob 객체나 File 객체에 접근할 수 있게 해준다.
👉 이 URL은 메모리를 사용하기 때문에 더 이상 필요하지 않을 때에는 해제해야 한다. - 비동기 작업이 아니라서 호출 즉시 URL을 해제한다.
URL.revokeObjectURL(objectURL);
- 매개변수
- objectURL
: createObjectURL()로 생성한 URL
- objectURL
✅ 사용
1. Object URL 생성
- URL.createObjectURL() 메서드를 호출하여 Blob 객체로부터 Object URL을 생성
const [file, setFile] = useState(null); // 사용자가 선택한 파일을 저장
const newObjectURL = URL.createObjectURL(file);
2. Object URL 사용
- 생성된 URL을 img 태그의 src, a 태그의 href, 다른 HTML 요소의 속성에 사용하여 데이터를 표시하거나 다운로드할 수 있다.
const [objectURL, setObjectURL] = useState(''); // objectURL 상태 업데이트
setObjectURL(newObjectURL);
3. Object URL 해제
- URL.revokeObjectURL() 메서드를 호출하여 더 이상 사용하지 않는 Object URL을 해제
URL.revokeObjectURL(newObjectURL);
예제) 노트 제목, 내용, 커버 이미지 파일을 입력받는 폼 컴포넌트
import { useImmer } from 'use-immer';
import { Form } from 'react-router-dom';
import { AppButton } from '@/components';
import { oneOf } from 'prop-types';
// 타입 검사
NoteForm.propTypes = {
method: oneOf('get', 'post', 'put', 'patch', 'delete').isRequired,
};
// 초기 상태 정의
const INITIAL_FORM_DATA = {
title: '',
description: '',
thumbnail: null,
};
function NoteForm({ method }) {
// 현재 상태
const [formData, setFormData] = useImmer(INITIAL_FORM_DATA);
// 폼의 입력 필드에서 값이 변경될 때 호출되는 함수
const handleChange = (e) => {
// 해당 필드의 값을 새로 입력된 값으로 설정
setFormData((draft) => {
draft[e.target.name] = e.target.value;
});
};
// 파일 입력 필드에서 파일이 선택될 때 호출되는 함수
const handleThumbnail = (e) => {
// 선택된 파일의 목록
const [file] = e.target.files;
// 파일 객체를 참조하는 임시 URL을 생성
// - 이 URL은 브라우저에서 파일을 직접 접근할 수 있게 해준다.
const imageUrl = URL.createObjectURL(file);
// 상태를 업데이트하여 선택한 파일의 URL을 thumbnail 필드에 저장
setFormData((draft) => {
draft.thumbnail = imageUrl;
});
};
// 폼을 초기 상태로 리셋하는 함수
const handleReset = () => {
// INITIAL_FORM_DATA로 상태를 설정하여 폼 초기화
setFormData(INITIAL_FORM_DATA);
};
// 폼의 제목과 설명이 모두 비어 있지 않은지 확인
// - 폼의 제출 버튼을 활성화할지 여부를 결정하는 데 사용
const isReady =
formData.title.trim().length > 0 && formData.description.trim().length > 0;
return (
<Form
method={method}
encType="multipart/form-data"
onReset={handleReset}
className="flex flex-col gap-5 w-full max-w-sm"
>
<div className="flex flex-col gap-1 w-full">
<label htmlFor="title" className="text-accent">
제목
</label>
<input
type="text"
name="title"
id="title"
className="border border-accent/20 rounded py-2 px-3 text-accent"
placeholder="노트 제목"
defaultValue={formData.title}
onChange={handleChange}
/>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="description" className="text-accent">
내용
</label>
<textarea
rows={3}
cols={20}
name="description"
id="description"
className="border border-accent/20 rounded py-2 px-3 min-h-24 text-accent"
placeholder="노트 내용을 작성합니다."
defaultValue={formData.description}
onChange={handleChange}
/>
</div>
<div className="flex flex-col gap-1">
<span className="text-accent">커버</span>
<div className="flex items-center justify-center w-full">
<div className="focus-within:border-accent flex flex-col items-center justify-center w-full h-44 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
<label
htmlFor="cover"
className="overflow-hidden cursor-pointer relative w-full flex flex-col items-center justify-center pt-5 pb-6"
>
<p className="text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">클릭 업로드</span> 하거나,
드래그 앤 드롭 하세요.
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
JPG, PNG, WEBP, AVIF
</p>
{formData.thumbnail && (
<div className="absolute top-0 flex rounded-md justify-center w-full h-full bg-accent/90">
<img
className="h-full aspect-auto"
src={formData.thumbnail}
alt=""
/>
</div>
)}
</label>
<input
id="cover"
name="cover"
type="file"
accept="image/jpg,image/png,image/webp,image/avif"
className="sr-only"
onChange={handleThumbnail}
/>
</div>
</div>
</div>
<div className="flex gap-2">
<AppButton
disabled={!isReady}
submit
buttonProps={{ className: 'flex-1' }}
>
저장
</AppButton>
<AppButton
reset
buttonProps={{
className: 'flex-1 bg-slate-400',
}}
>
취소
</AppButton>
</div>
</Form>
);
}
export default NoteForm;