Skip to content

React에서의 DOM 요소 접근 (useRef vs getElementById) ‐ 2024.11.07

Dongwoo Ko edited this page Nov 21, 2024 · 1 revision

문제 상황

export default function StockIndex() {
  return (
    <div className='flex gap-2'>
      <Chart name='코스피' />
      <Chart name='코스닥' />
    </div>
  );
}

다음과 같은 코드에서 첫번째 Chart 컴포넌트에서만 canvas가 그려지는 문제가 발생했다.

의도한 결과대로라면 각 컴포넌트 내에서 canvas가 그려져야 한다.

Screen.Recording.2024-11-07.at.1.13.06.PM.mov

영상을 잘 보면 중간에 차트의 움직임이 깔끔하지 못하고 버벅이는 움직임까지 확인할 수 있다.

각 chart 컴포넌트의 주가 변동 로직은 잘 동작하지만 두 컴포넌트 모두 한 canvas에 그리고 있는 듯한 느낌이 든다.

문제 분석

chart 컴포넌트의 코드를 한 번 살펴보자

export function Chart({ name }: StockIndexChartProps) {
  const [prices, setPrices] = useState<number[]>([50, 54]);

	//... 생략

  useEffect(() => {
    **const canvas = document.getElementById('lineChart'); // 이 부분이 문제!**
    const ctx = canvas?.getContext('2d');
    if (!ctx) return;

    drawChart(ctx, prices);
  }, [prices]);

  return (
    <div className='flex h-[200px] w-[500px] items-center rounded-lg bg-juga-grayscale-50 p-5'>
      // ... 생략
      <canvas
        id='lineChart'
        width={600}
        height={300}
        className='flex-1 h-full'
      />
    </div>
  );
}

canvas에 접근하기 위해 document.getElementById 를 사용하고 있다. 이 부분이 문제이다!

id가 ‘lineChart’라는 canvas를 각 컴포넌트마다 새로 생성하고 있지만 getElementById 를 사용하면 DOM을 탐색하면서 id가 ‘lineChart’라는 요소를 처음으로 발견하면 그 요소를 즉시 반환한다. 이 때문에 component가 여러 개 생성되어도 모두 첫 번째 canvas에만 접근하게 된다.

‘id’라는 속성이 고유한 요소를 식별하기 위한 것인데 이러한 코드 구조라면 동일한 id를 가진 여러 요소가 생길 우려가 있다.

문제 해결

useRef 를 사용하면 이 문제를 해결할 수 있다.

useRef는 각 컴포넌트 인스턴스마다 고유한 값, 요소를 참조할 수 있다.

즉, 우리가 처한 상황에서는 useRef를 활용해 각 컴포넌트마다 가지는 canvas를 참조해 문제를 해결할 수 있다.

Screen.Recording.2024-11-07.at.5.32.08.PM.mov
export function Chart({ name }: StockIndexChartProps) {
  const [prices, setPrices] = useState<number[]>([50, 54]);
  **const canvasRef = useRef<HTMLCanvasElement>(null);**
  
	//... 생략

  useEffect(() => {
    **const canvas = canvasRef.current;**
    const ctx = canvas?.getContext('2d');
    if (!ctx) return;

    drawChart(ctx, prices);
  }, [prices]);

  return (
    <div className='flex h-[200px] w-[500px] items-center rounded-lg bg-juga-grayscale-50 p-5'>
      // ... 생략
      <canvas
        **ref={canvasRef}**
        width={600}
        height={300}
        className='flex-1 h-full'
      />
    </div>
  );
}

++ (갑자기 생긴 궁금증) 위 코드에서 useRef, useEffect hooks는 어떤 흐름으로 렌더링되고 처리될까?

image

위 hooks licescycle을 참고해 정리해보자

  1. useState(), useRef() 실행
    • useState() - prices 상태 변수 [50, 54]로 초기화
    • useRef() - canvasRef를 {current:null} 상태로 초기화
  2. return() 실행 (렌더링 요소 반환)
    • JSX 구조를 파악하고 DOM 요소 생성
    • 이 과정에서 canvasRef가 canvas 요소와 연결
  3. useEffect()실행
    • canvasRef에 값이 있기 때문에 canvasRef.current로 canvas 요소에 접근 가능
    • canvas에 drawing

Ref

https://github.com/Wavez/react-hooks-lifecycle
https://velog.io/@denmark-choco/React-Lifecycle-React-Hook-lifeCycle#3-effect%EB%A5%BC-%EA%B1%B4%EB%84%88%EB%9B%B0%EC%96%B4-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94

📜 개발 일지

⚠️ 트러블 슈팅

❗ 규칙

🗒️ 기록

기획
회의록
데일리스크럼
그룹 멘토링
그룹 회고

😲 개별 멘토링

고동우
김진
서산
이시은
박진명
Clone this wiki locally