Front-End/React

[React] 이미지 파일 미리보기 (Feat. createObjectURL)

시니철 2024. 10. 10. 13:45

1. AddItem

useState Hook을 사용하여 `values` 상태를 관리하여 여기에 `imgFile`이라는 이미지 파일을 저장합니다.

const [values, setValues] = useState({
  imgFile: null,
});

 

`handleChange` 함수는 자식 컴포넌트 `FileInput`에서 전달받은 파일 정보를 `values` 상태에 업데이트 합니다.

const handleChange = (name, value) => {
  setValues({
    [name]: value,
  });
};

 

이제 `values`와 `handleChange` 함수를 `FileInput` 컴포넌트에 prop으로 내려줍니다.

<FileInput
    name="imgFile"
    value={values.imgFile}
    onChange={handleChange}
/>

2. FileInput

a. 파일 선택 시 미리보기 이미지 설정

`handleChange` 함수는 사용자가 파일을 선택할 때 호출 됩니다. `FileInput`의 `onChange` 이벤트 핸들러를 통해 사용자가 선택한 파일 정보를 `AddItemWrapper` 컴포넌트로 전달합니다.

const handleChange = (e) => {
  const nextValue = e.target.files[0];
  onChange(name, nextValue);
};

 

 

b. preview 상태 관리

useState Hook을 사용하여 `preview` 상태를 관리합니다.

const [preview, setPreview] = useState();

 

 

c. 미리보기 URL 생성

사용자가 파일을 선택할 때마다 useEffect Hook이 실행됩니다. 여기서 `URL.createObjectURL()`을 사용해 선택한 파일에 대한 미리보기 URL을 생성하고 `preview` 상태에 업데이트 합니다. 이 URL은 임시적으로 생성되며 컴포넌트가 언마운트되거나 이미지가 변경될 때 `URL.revokeObjectURL()`로 메모리를 해제합니다.

useEffect(() => {
  if (!value) return;
  const nextPreview = URL.createObjectURL(value);
  setPreview(nextPreview);

  return () => {
    setPreview();
    URL.revokeObjectURL(nextPreview);
  };
}, [value]);

 

 

d. 이미지 초기화 버튼

이미지가 선택된 경우에만 `X` 버튼이 표시됩니다. 이 버튼을 클릭하면 useRef Hook을 사용하여 input의 value인 파일 입력 필드의 값을 지우고, AddItem에 null을 전달해 이미지를 초기화 합니다.

const inputRef = useRef();

const handleClearClick = () => {
  const inputNode = inputRef.current;
  if (!inputNode) return;

  inputNode.value = "";
  onChange(name, null);
};

3. 컴포넌트 렌더링

렌더링 시, 사용자가 선택한 이미지의 미리보기와 파일 입력 필드가 나타납니다. 파일을 선택하면 이미지 미리보기가 보이며, `X` 버튼을 클릭하면 파일이 초기화됩니다.

return (
  <>
    <img src={preview} alt="이미지 미리보기" />
    <input
      type="file"
      accept="image/png, image/jpeg"
      onChange={handleChange}
      ref={inputRef}
    />
    {value && <button onClick={handleClearClick}>X</button>}
  </>
);

전체 코드

// AddItem 컴포넌트
const AddItem = () => {
  const [values, setValues] = useState({
    imgFile: null,
  });

  const handleChange = (name, value) => {
    setValues({
      ...values,
      [name]: value,
    });
  };

  return (
    <div>
      <h1>이미지 업로드</h1>
      <FileInput
        name="imgFile"
        value={values.imgFile}
        onChange={handleChange}
      />
    </div>
  );
};
// FileInput 컴포넌트
const FileInput = ({ name, value, onChange }) => {
  const [preview, setPreview] = useState();
  const inputRef = useRef();

  const handleChange = (e) => {
    const nextValue = e.target.files[0];
    onChange(name, nextValue);
  };

  // 미리보기 URL 생성
  useEffect(() => {
    if (!value) return;
    const nextPreview = URL.createObjectURL(value);
    setPreview(nextPreview);

    return () => {
      setPreview();
      URL.revokeObjectURL(nextPreview);
    };
  }, [value]);

  const handleClearClick = () => {
    const inputNode = inputRef.current;
    if (!inputNode) return;

    inputNode.value = "";
    onChange(name, null);
  };

  return (
    <>
      <img src={preview} alt="미리보기" />
      <input
        type="file"
        accept="image/png, image/jpeg"
        onChange={handleChange}
        ref={inputRef}
      />
      {value && <button onClick={handleClearClick}>X</button>}
    </>
  );
};