useRef vs variable, useState 차이점

pks787·2020년 4월 13일
2

Hooks를 이용해 개발하던 도중 일반 변수(variable)와 useRef의 차이점에 대해 궁금해져서 공부를 한 내용을 정리해보자 한다.

1. useRef vs useState

1-1 useState를 사용한 예시 코드

function Counter() {
  const [counter, setCounter] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      setCounter(counter => counter + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
      alert(counter);
    };
  }, []);
  return (
    <div>
      <p>{counter}</p>
    </div>
  );
}

위의 코드대로 작성하면 counter가 1초마다 증가하는 rendering은 정상적으로 나타나지만 alert은 항상 0으로 출력된다.
useEffect의 dependency에 counter가 의존 되지 않는 상태이기에 아래 같이 변경해줘야 한다.

useEffect(() => {
  const timer = setInterval(() => {
    setCounter(counter => counter + 1);
  }, 1000);
  return () => {
    clearInterval(timer);
    alert(counter);
  };
}, [counter]);

하지만 위의 코드대로 수정하면 이렇게 만들어버리면 counter가 변할 때마다 useEffect가 trigger되니까 원래 로직이랑 달라지게 되며 이러한 현상은 useState가 closure 안의 값까지 업데이트해줄 수는 없는 것으로 설명된다.

1-2 useRef를 사용한 예시 코드

위의 useState를 이용한 코드를 보면 counter가 변할 때 마다 렌더를 하고 싶은 경우

const value = useRef(0);
const [count, setCount] = useState(value.current);

위의 코드를 사용해서 적용하고
매번 렌더할 필요가 없는 아래와 같은 경우는

const counter = useRef(0) 

이렇게 작성하여 코딩을 하면 될 것이다.

위의 useState를 사용한 예시 코드는 굳이 리렌더를 할 필요가 없기에
후자 코드인 const counter = useRef(0)를 사용하면 될 것 같다.

function CounterKai() {
  const counter = useRef(0);
  useEffect(() => {
    const timer = setInterval(() => {
      counter.current += 1;
    }, 1000);
    return () => {
      clearInterval(timer);
      alert("<CounterKai/>:", counter.current);
    };
  }, []);
  return (
    <div>
      <p>{counter.current}</p>
    </div>
  );
}

2. useRef vs variable 비교

2-1 variable를 사용한 예시 코드

import React, { useState, useEffect, useRef } from 'react';

// defined a variable outside function component
let countCache = 0;

function Counter() {
  const [count, setCount] = useState(0);
  countCache = count;       // set default value

  useEffect(() => {
    setTimeout(() => {
      // We can get the latest count here
      console.log(`You clicked ${countCache} times (countCache)`);
    }, 3000);
  });
  // ...
}

export default Counter;
  • 위와 같이 let variable을 사용하면 자바스크립트 클로저 기능으로 인해 Counter가 소멸되면서 전역 변수인 let countCache를 참조하여 항상 같은 값인 0을 가리키게 되어 원하는 결과를 도출하지 못한다

2-2 useRef를 사용한 예시 코드

function Example() {
  const [count, setCount] = useState(0);
  const latestCount = useRef(count);

  useEffect(() => {
    // Set the mutable latest value
    latestCount.current = count;
    setTimeout(() => {
      // Read the mutable latest value
      console.log(`You clicked ${latestCount.current} times`);
    }, 3000);
  });
  // ...
}

useRef는 공식문서에 의하면 아래와 같이 정의 된다.

useRef는 .current 프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환합니다. 반환된 객체는 컴포넌트의 전 생애주기를 통해 유지될 것입니다.
이 기능은 클래스에서 인스턴스 필드를 사용하는 방법과 유사한 어떤 가변값을 유지하는 데에 편리합니다.

위의 문장처럼 useRef는 컴포넌트의 전 생명주기를 통해 유지되는 값이라는 의미이며, 순수한 자바스크립트 객체를 생성 및 유지시켜주기 때문에 closure 이슈가 발생하지 않는다는 걸 알 수 있다


  • https://cereme.dev/blog/react-hooks-useeffect-useref-feat-closure/
  • https://stackoverflow.com/questions/57444154/why-need-useref-to-contain-mutable-variable-but-not-define-variable-outside-the
profile
Front End. Dev

2개의 댓글

comment-user-thumbnail
2020년 8월 16일

안녕하세요. 읽고 궁금한 점이 있어서 질문 남깁니다.

  1. "2-1 variable를 사용한 예시 코드" 와 "useRef를 사용한 예시 코드"은 결과 값이 같지 않나요? 제가 직접 돌렸을 때는 전역 변수를 써도 setCounter로 counter 값을 변경하면 0이 아닌 변경 된 값이 정상적으로 출력되던데, 혹시 클로져 이슈라는게 무슨 말일까요??

  2. 클로저의 기능은 클로저 안의 정의된 함수는 만들어진 환경을 소멸시키지 않고 기억하는 것인데, "자바스크립트 클로저 기능으로 인해 Counter가 소멸되면 원하는 결과를 도출하지 못한다"이 어떤 의미인가요??

감사합니다.

1개의 답글