DEV Community

Cover image for useRef()가 순수 자바스크립트 객체라는 의미를 곱씹어보기
Dylan
Dylan

Posted on

useRef()가 순수 자바스크립트 객체라는 의미를 곱씹어보기

리액트에서는 DOM 노드나 React 엘리먼트에 접근하기 위한 방법으로 Ref라는 API를 제공해왔습니다.

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  focusTextInput() {
    this.textInput.current.focus();
  }
  render() {
    return (
      <div>
        <input
          type="text"
          ref={this.textInput}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

일반적으로는 이렇게 DOM에 접근하기 위해 Ref를 사용해왔습니다.
React 16.8 버전부터 새롭게 등장한 hook을 이용해 useRef 라는 API가 공개됐습니다. 이전 버전의 createRef를 단순히 hook 으로 처리했다라고만 알고 있었습니다. 그런데 react-reduxuseSelector 소스코드에서 useRef 를 적극적으로 사용하는 것을 보고 의문이 생겼습니다.

Alt Text
공식문서에 설명된 목적 (DOM 노드나 React 엘리먼트에 접근하기 위한 방법)이 아니라 일종의 변수를 관리하기 위한 목적으로 사용하고 있었습니다.
react-redux 팀이 공식문서에서 벗어난 방법을 쓸 것 같지는 않아서 기존에 봐왔던 createRef 가 아니라 useRef 문서를 찾아보았습니다.

useRef() is useful for more than the ref attribute. It's handy for keeping any mutable value around similar to how you'd use instance fields in classes.
The useRef() Hook isn't just for DOM refs. The "ref" object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class.

This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.
Keep in mind that useRef doesn't notify you when its content changes. Mutating the .current property doesn't cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

useRef() 는 DOM 뿐만이 아니라 어떤 값이든 저장할 수 있는 일반적인 자바스크립트 객체라는 표현을 찾을 수 있었습니다. 또한 부연설명으로 매번 렌더링할 때 동일한 객체를 제공한다는 점과 값이 변경될 때 리렌더링이 발생하지 않는다는 점도 써있었습니다. 만약에 DOM 노드에 ref 를 더하거나 제거할때 어떤 코드를 실행시키고 싶다면 uesRef 가 아니라 기존의 callback ref 를 사용하라고도 합니다.


일반적인 프로그래밍 언어는 heap 영역과 stack 영역에서 메모리를 관리합니다(code와 data 영역이 있지만 이 글에서는 주제를 벗어나니 생략하겠습니다). stack 공간은 자바스크립트의 single thread call stack을 말하는 그 stack으로, 함수가 실행될 때 메모리에 할당됐다가 종료되면서 한꺼번에 해제됩니다.

반면 heap 은 전역변수와 참조타입의 변수를 할당하고 가비지 컬렉터를 이용해 사용하지 않는(=참조되지 않는) 메모리를 해제시킵니다. 즉 우리가 자바스크립트 객체로 만드는 변수들은 모두 heap 공간에 할당되었다가 해제됩니다.


다시 useRef 의 설명으로 돌아가보겠습니다.

1. useRef() 는 일반적인 자바스크립트 객체입니다.

heap 영역에 저장되는 변수입니다.

2.매번 렌더링할 때 동일한 객체를 제공합니다.

heap에 저장되어 있기 때문에 어플리케이션이 종료되거나 가비지 컬렉팅될 때 까지, 참조할때마다 같은 메모리 값을 가진다고 할 수 있습니다.

3.값이 변경되어도 리렌더링이 되지 않습니다.

같은 메모리 주소를 갖고있기 때문에 자바스크립트의 === 연산이 항상 true 를 반환합니다. 즉 변경사항을 감지할 수 없어서 리렌더링을 하지 않는다는 뜻입니다.

실제로 useRef 가 어떻게 만들어져 있는지 코드를 열어보았습니다.

Alt Text
useRef는 resolveDispatcher로 만들어진 useRef() 값을 return 합니다. 이 resolveDispatcher 코드를 따라가보면

Alt Text
resolveDispatcherReactCurrentDispatcher로 만들어지고

Alt Text
Alt Text
ReactCurrentDispatcher{ current: null } 이라는 자바스크립트의 plain object 로서, ReactSharedInternal에 저장되어 사용됩니다. 즉 useRef는 공식문서의 설명 그대로 plain object로 만들어진 hook 이었습니다.

createRef 가 잘 동작하는데, React팀은 왜 useRef를 만들었을까 추측해보았습니다. 함수형 컴포넌트는 인스턴스를 리턴하는 클래스형 컴포넌트와 조금 다르게 작동합니다. 렌더링 될때마다 매번 새로운 변수를 스택에 할당해 값이 초기화되기도 하고, 불필요한 성능낭비를 하게 될 수 있습니다. 클래스 컴포넌트는 인스턴스를 생성해서 렌더링 메소드만 재실행하는 구조였다면, 함수형 컴포넌트는 매번 함수(=함수형 컴포넌트의 렌더링) 를 실행하기 때문입니다.

함수형 컴포넌트에서 변수를 다루기 쉽게 하기위해 (마치 클래스의 인스턴스 변수처럼) 만들어진 API 입니다. 다른 변수선언 방법과 차이를 비교해보면 다음과 같습니다.

1. hook 기반의 useState 혹은 useContext 로 선언

이렇게 선언한 변수들은 값이 바뀔때마다 re-rendering을 유발합니다. 렌더링과 상관없는 변수를 선언하기에 적당하지 않습니다.

2. 함수형 컴포넌트 내부에 const 혹은 let , var 로 선언

렌더링 될 때마다 값이 초기화 됩니다. 컴포넌트의 생애주기 동안 관리해야하는 변수를 선언하기에 적당하지 않습니다.

3. 컴포넌트 바깥에 const 혹은 let , var 로 선언

불필요한 렌더링을 유발하지도 않고, 렌더링될 때 값이 초기화 되지도 않습니다. 하지만 컴포넌트를 재사용하면서 값을 각각 따로 관리하는게 불가능합니다.

let componentId = null;

function MyComponent(props) {
  componentId = props.id;
  return <h1>This is a component</h1>
}

이 경우 MyComponent 를 여러번 재사용하더라도 componentId 는 어플리케이션 내에 단 1개만 존재합니다.

4. useRef 를 이용해 선언

useRef 를 통해 선언된 변수는 리렌더링을 유발하지도 않고, 리렌더링될 때도 이전의 값을 기억하고 있으며, 컴포넌트마다 각각의 값을 가질 수 있습니다.

결론

useRef는 클래스의 인스턴스 프로퍼티와 같다고 생각하시면 됩니다. 컴포넌트 내부에서 관리하는 변수인데, 값이 바뀔 때마다 렌더링이 필요하면 useState 를 쓰면 되고 아닐 경우 useRef 를 써야한다고 생각하시면 간단할 것 같습니다.


출처
리액트 공식문서
https://stackoverflow.com/questions/54620698/whats-the-difference-between-useref-and-createref
https://blog.bitsrc.io/react-useref-and-react-createref-the-difference-afedb9877d0f
https://stackoverflow.com/questions/57530446/difference-between-useref-and-normal-variable
https://www.codementor.io/@dhananjaykumar/useref-hook-as-mutable-ref-object-ykb77fwvk
https://blog.logrocket.com/deep-dive-into-react-fiber-internals/

제가 틀린 부분이 있다면 적극적으로 알려주시면 감사하겠습니다^^

Top comments (4)

Collapse
 
reedids profile image
청설모

좋은 내용 감사합니다 :)
dev.to에 한국어 글이 더 많아지면 좋겠습니다.

Collapse
 
dylanju profile image
Dylan • Edited

읽어주셔서 감사합니다~! 미디엄쓰다 넘어왔는데 조금 더 편한것 같네요 ㅎㅎ

Collapse
 
dance2die profile image
Sung M. Kim • Edited

유익한 포스트 잘 읽었습니다.

쉽게 풀이 해 주시니 이해가 잘되네요

Collapse
 
dylanju profile image
Dylan

읽어주셔서 감사합니다^^