React

[React/리액트]React에서 요소에 접근과 제어하는 방법 / useRef와 getElementByIdref 비교 / input에 useRef를 쓰는 이유

solfa 2024. 7. 28. 21:17

https://5ffthewall.tistory.com/112

 

[React/리액트] 파일 업로드와 미리보기 기능 구현하기

구현할 기능 및 문제 상황다음과 같이 갤러리에서 이미지를 업로드하는 기능을 구현해야 했다.각자 다른 페이지에서 두 번 씩이나 쓰이기 때문에 이미지를 등록하는 기능을 hook으로 구현해서

5ffthewall.tistory.com

 

 

위 글에서 파일 업로드를 구현하다가 궁금증이 생겼다. 대부분의 파일 업로드 코드들이 useRef를 사용해서 나도 useRef로 값을 참조했는데 useState를 써도 될 것 같다는 생각이 들었다. 구글링하다가 useRef를 안좋아한다는 사람도 있고.... useRef 쓸 바에 useState쓴다는 말도 있고... 그래서 직접 코드를 비교해보기로 했다!

useRef 사용 이유

useRef 훅은 React에서 DOM 요소에 직접 접근하기 위해 사용되된다. 인터넷에서 파일 입력, 이미지 업로드, input 태그를 이용한 업로드에서 useRef를 사용하는 이유는 숨겨진 입력의 참조가 가능해서 파일 입력 요소를 숨기고, 버튼 클릭 시 파일 선택 창을 열기 위해서라고 했다. 하지만 이것들은 useState를 써도 구현이 가능한데!! 열심히 코드를 비교하다보니 결론이 나왔다.

 

useRef 사용 ver.

import Back from '../../assets/RegisterStoreInfo/back.svg'
import {
...
} from './FirstRegisterStoreInfo.style'
import UploadImg from '../../assets/RegisterStoreInfo/upload.svg'
import nav from '../../assets/RegisterStoreInfo/firststep.svg'
import { useNavigate } from 'react-router-dom'
import { useRef } from 'react'
import useImageUploader from '../../hooks/useImageUpload'

export default function FirstRegisterStoreInfo() {
  const navigate = useNavigate()
  const fileInputRef = useRef<HTMLInputElement>(null)
  const { selectedImage, handleUpload } = useImageUploader()

  return (
    <StyledContainer>
      ...
            <StyledUploadBox onClick={() => fileInputRef.current?.click()}>
              {selectedImage ? (
                <img
                  src={selectedImage.thumbnail}
                  alt="Preview"
                  style={{ width: '100%', height: '100%', objectFit: 'cover' }}
                />
              ) : (
                <>
                  <StyledUploadImg src={UploadImg} alt="업로드 아이콘" />
                  <StyledUploadText>
                    갤러리에서 이미지 등록하기
                  </StyledUploadText>
                </>
              )}
            </StyledUploadBox>
            <input
              type="file"
              ref={fileInputRef}
              style={{ display: 'none' }}
              onChange={handleUpload}
            />
         ...
    </StyledContainer>
  )
}

 

이 코드는

1. input 태그에 ref를 사용해서 input, 즉 업로드 된 이미지를 참조한다! 

2. fileInputRef.current?.click(), 즉 ref.current를 사용해서 클릭을 감지한다!

 + current.focus 같은 함수도 있음

 

useRef 사용 안 한 ver.

import Back from '../../assets/RegisterStoreInfo/back.svg'
import {
...
} from './FirstRegisterStoreInfo.style'
import UploadImg from '../../assets/RegisterStoreInfo/upload.svg'
import nav from '../../assets/RegisterStoreInfo/firststep.svg'
import { useNavigate } from 'react-router-dom'
import { useState } from 'react'

export default function FirstRegisterStoreInfo() {
  const navigate = useNavigate()
  const [selectedImage, setSelectedImage] = useState<File | null>(null)

  const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const fileList = e.target.files
    if (fileList && fileList[0]) {
      setSelectedImage(fileList[0])
    }
  }

  return (
    <StyledContainer>
     ...
            <StyledLabel>가게 배너 사진 등록</StyledLabel>
            <StyledUploadBox
              onClick={() => document.getElementById('file-input')?.click()}
            >
              {selectedImage ? (
                <img
                  src={URL.createObjectURL(selectedImage)}
                  alt="Preview"
                  style={{ width: '100%', height: '100%', objectFit: 'cover' }}
                />
              ) : (
                <>
                  <StyledUploadImg src={UploadImg} alt="업로드 아이콘" />
                  <StyledUploadText>
                    갤러리에서 이미지 등록하기
                  </StyledUploadText>
                </>
              )}
            </StyledUploadBox>
            <input
              type="file"
              id="file-input"
              style={{ display: 'none' }}
              onChange={handleUpload}
            />
   ...
    </StyledContainer>
  )
}

 

useRef 대신 useState를 사용했다.

파일 입력 요소를 숨기는 건

style={{ display: 'none' }}

 

이 스타일을 주었고,

 

input태그에 ref로 값을 참조하는 것 대신 직접 id를 할당한 후 getElementById를 사용해서 클릭을 감지하는 방식으로 구현했다.

 <input
              type="file"
              id="file-input"
              style={{ display: 'none' }}
              onChange={handleUpload}
            />
            
            
            
onClick={() => document.getElementById('file-input')?.click()}

 

이런 식으로!! 이벤트 발생 순서등의 차이는 있겠지만 외관상 ㅎ 두 코드는 똑같이 작동한다.

 

그렇다면 왜 useRef를 사용하는 것일까?

 

생각해보니까 그 이유는 간단했다.

 

useRef를 사용하는 것이 더 React스러운 코드이기 때문이다!

 

 

React에서 요소에 접근하는 방법에는 useRef 와 getElementById 두 가지가 있다. 비교를 해보자면

 

1. getElementById로 요소 접근하기

document.getElementById는 전통적인 JavaScript 메서드로 HTML 문서에서 특정 ID를 가진 요소를 선택할 때 사용된다.

const element = document.getElementById('myElement');

 

2. useRef로 요소 접근하기

useRef는 React 훅 중 하나로 함수형 컴포넌트에서 DOM 요소에 접근할 수 있게 해준다.

const MyComponent = () => {
  const myElementRef = useRef(null);
  const handleClick = () => {
    console.log(myElementRef.current);
  };
  return (
    <div>
      <div ref={myElementRef}>Hello, World!</div>
      <button onClick={handleClick}>Click Me</button>
    </div>
  );
};

 

그렇다면 무엇을 사용해야 좋을까?

 -> React에서는 useRef를 사용하는 것이 좋다.

 

1. 선언적 접근 방식

React는 선언적 프로그래밍 패러다임을 따른다. document.getElementById는 명령형 접근 방식으로 React의 선언적 접근 방식과 상충된다.

 

2. 가상 DOM과의 충돌 방지

React는 가상 DOM을 사용하여 실제 DOM과의 차이를 계산하고 필요한 부분만 업데이트한다. document.getElementById로 직접 DOM을 조작하면 가상 DOM과 실제 DOM 간의 일관성이 깨질 수 있다.

 

3. DOM에 직접 접근하게 되는 문제

getElementById를 사용하는 것은 DOM에 직접 접근하는 것이다. React에서는 document.getElementById와 같은 직접적인 DOM 접근을 하지 않고, useRef를 사용하라고 가이드하고 있다.

https://ko.react.dev/learn/manipulating-the-dom-with-refs

 

4. 성능 문제

useRef는 컴포넌트가 리렌더링될 때에도 동일한 참조 객체를 유지한다. 반면, document.getElementById는 매번 DOM을 탐색하기 때문에 성능에 영향을 미칠 수 있다.​

 

5. 코드의 안전성 및 가독성 문제

document.getElementById를 사용하면 같은 ID를 가진 여러 요소가 존재할 경우 예기치 않은 동작을 할 수 있다. 반면, useRef는 컴포넌트 내에서 고유하게 관리되기 때문에 이러한 충돌을 피할 수 있다.

 

결론은! 두 가지 방법 모두 파일 업로드 기능을 구현할 수 있지만, React의 권장 방식은 useRef를 사용하는 것이다!

따라서 난 useRef를 사용해서 파일 업로드를 구현했다.

 

728x90