리액트 이미지 파일 미리보기

웅평·2023년 8월 3일
0

이미지 파일 미리보기

파일 객체를 ObjectURL로 만들면 파일에 대한 주소를 만들 수 있다
인터넷에 올린 파일 링크 같이 사용자 컴퓨터에 있는 파일을 주소로 사용할 수 있다

mport { useEffect, useRef, useState } from "react";

// 이미지 파일 선택 창
function FileInput({ name, value, onChange }) {
    // 파일 미리보기 State
    const [preview, setPreview] = useState();

    // Ref 객체 생성 
    const inputRef = useRef();

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


    // 선택 파일 초기화
    const handleClearClick = () => {
        const inputNode = inputRef.current;
        if (!inputNode) return;

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

    // 파일을 선택할 떄마다 미리 보기 주소 변경
    useEffect(() => {
        if (!value) return;

        const nextPreview = URL.createObjectURL(value);
        setPreview(nextPreview);
    }, [value]);
  
    return (
        <div>
            <img src={preview} alt="이미지 미리보기" />
            <input type="file" onChange={handleChange} ref={inputRef}/>
            {value && <button onClick={handleClearClick}>X</button>}
        </div>
    );
}
  
export default FileInput;

  • ObjectURL은 URL.createObjectURL() 함수로 만들 수 있다. 이 함수는 문자열을 리턴한다
  • blob으로 시작하는 임시로 생선된 주소가 있다 이것이 ObjectURL이다

사이드 이펙트(Side Effect)

ObjectURL을 만들면 웹 브라우저는 메모리를 할당하고 파일에 해당하는 주소를 만들어 준다
랜더링하는 과정에서 리액트 외부의 상태를 바꾸게 되는데 이럭식으로 컴포넌트 함수에서 외부의 상태를 바꾸는 걸 사이드 이펙트라고 한다

사이드 이펙트와 useEffect
useEffect 는 리액트 컴포넌트 함수 안에서 사이드 이펙트를 실행하고 싶을 때 사용하는 함수이다
예를들어

  • DOM 노드를 직접 변경
  • 브라우저에 데이터를 저
  • 네트워크 리퀘스트를 보내는 것
    등 주로 리액트 외부에 있는 데이터나 상태를 변경할 때 사용

페이지 정보 변경

useEffect(() => {
  document.title = title; // 페이지 데이터를 변경
}, [title]);

네트워크 요청

useEffect(() => {
  fetch('https://example.com/data') // 외부로 네트워크 리퀘스트
    .then((response) => response.json())
    .then((body) => setData(body));
}, [])

데이터 저장

useEffect(() => {
  localStorage.setItem('theme', theme); // 로컬 스토리지에 테마 정보를 저장
}, [theme]);

메모리 삭제

revokeObjectURL를 통해서 사이드 이펙트를 해제할 수 있다

	useEffect(() => {
        if (!value) return;

        const nextPreview = URL.createObjectURL(value);
        setPreview(nextPreview);
        
        // 다른 파일 선택하거나 해제했을 때 사이드 이펙트 해제
        return () => {
            setPreview();
            URL.revokeObjectURL(nextPreview);
        }
    }, [value]);

이 함수는 나중에 디펜던시 리스트 값이 바뀌어서 새로 콜백을 실행하게 되는데 새로 콜백을 실행하기 전에 리액트는 앞에서 리턴한 정리 함수를 실행해서 사이드 이펙트를 정리한다

정리 함수 (Cleanup Function)

useEffect(() => {
  // 사이드 이펙트

  return () => {
    // 사이드 이펙트에 대한 정리
  }
}, [dep1, dep2, dep3, ...]);

useEffect 의 콜백 함수에서 사이드 이펙트를 만들면 정리가 필요한 경우가 있다.
이럴 때 콜백 함수에서 리턴 값으로 정리하는 함수를 리턴할 수 있었는데 리턴한 정리 함수에서는 사이드 이펙트에 대한 뒷정리를 한다.

예를 들면 이미지 파일 미리보기를 구현할 때 Object URL을 만들어서 브라우저의 메모리를 할당(createObjectURL) 했는데 정리 함수에서는 이때 할당한 메모리를 다시 해제(revokeObjectURL)했다

정리 함수가 실행되는 시점
쉽게 말해서 콜백을 한 번 실행했으면 정리 함수도 반드시 한 번 실행된다고 생각하면 된다

정확히는 새로운 콜백 함수가 호출되기 전에 실행되거나 (앞에서 실행한 콜백의 사이드 이펙트를 정리), 컴포넌트가 화면에서 사라지기 전에 실행된다 (맨 마지막으로 실행한 콜백의 사이드 이펙트를 정리)

예시: 타이머

import { useEffect, useState } from 'react';

function Timer() {
  const [second, setSecond] = useState(0);

  useEffect(() => {
    const timerId = setInterval(() => {
      console.log('타이머 실행중 ... ');
      setSecond((prevSecond) => prevSecond + 1);
    }, 1000);
    console.log('타이머 시작 🏁');

    return () => {
      clearInterval(timerId);
      console.log('타이머 멈춤 ✋');
    };
  }, []);

  return <div>{second}</div>;
}

function App() {
  const [show, setShow] = useState(false);

  const handleShowClick = () => setShow(true);
  const handleHideClick = () => setShow(false);

  return (
    <div>
      {show && <Timer />}
      <button onClick={handleShowClick}>보이기</button>
      <button onClick={handleHideClick}>감추기</button>
    </div>
  );
}

export default App;

일정한 시간 간격마다 콜백 함수를 실행하는 setInterval 이라는 함수도 정리가 필요한 사이드 이펙트다

  1. 이 컴포넌트는 렌더링이 끝나면 타이머를 시작하고, 화면에서 사라지면 타이머를 멈춥니다.
  2. 사용자가 '보이기' 버튼을 눌렀을 때 show 값이 참으로 바뀌면서 다시 렌더링 됩니다.
  3. 조건부 렌더링에 의해서 Timer 컴포넌트를 렌더링 하는데 Timer 컴포넌트에서는 useEffect 에서 타이머를 시작하고, 정리 함수를 리턴한다
  4. 콘솔에는 '타이머 시작 🏁'이 출력
  5. 다시 사용자가 '감추기' 버튼을 누르면 show 값이 거짓으로 바뀌면서 다시 렌더링 된다
  6. 조건부 렌더링에 의해서 이제 Timer 컴포넌트를 렌더링 하지 않는다
  7. 그럼 리액트에선 마지막으로 앞에서 기억해뒀던 정리 함수를 실행해
  8. 타이머를 멈추고 콘솔에는 '타이머 멈춤 ✋'이 출력
    이런 식으로 정리 함수를 리턴하면 사이드 이펙트를 정리하고 안전하게 사용할 수 있다

참고
코드잇

0개의 댓글