
리액트 공식문서 중 Escape Hatches 의 첫 번째 강을 읽고 내 마음대로 정리하는 내용
리액트에서 컴포넌트를 정의 할 때
컴포넌트 내부의 변수는 const , let , var 등과 같은 변수 선언문 혹은 useState , useReducer 등을 이용해
선언해주었다.
이 때 state 형태로 선언되는 변수들은 값이 변하게 되면 리렌더링이 되기 때문에
값이 변하지 않는 값을 선언하기 위해선 변수 선언문인 const , let , var 등을 이용해야 한다.
그런데 문득 생각이 든다.
컴포넌트가 재호출될 때 마다 const , let , var 과 같은 변수 선언문이 항상 다시 선언될 텐데
(어차피 선언 될 때 마다 다른 지역 환경이기 때문에 const 라고 하더라도 재할당이 된다)
만약 선언하고자 하는 변수가 선언하는데까지 오래 걸리는 변수라면 ?
컴포넌트가 재호출 될 때 마다 변수를 선언하는데 오랜 시간이 걸릴 것이다.

컴포넌트 외부에서 변수를 선언해두고 캐싱해두고 싶은 마음이 굴뚝 같아진다.
그 ~ 때 ~ 사용하는 것이 바로 Ref 이다.
useRef 컴포넌트에 추가하기
useRef 는 컴포넌트 내부에서만 사용 가능하다.
컴포넌트 외부에서 선언된 useRef 는 컴파일 에러를 야기한다.
useRef 로 생성된 Ref 객체는 current 프로퍼티에 값을 저장하고 있다.

useRef 가 필요한 경우를 생각해보기let 변수 선언문로 선언했을 때

스톱워치를 만들어보았다.
이 때 스톱워치 기능을 할 setInterval 의 id 값을 IntervalId 라는 변수에 선언해주고 Stop 버튼이 눌리면
IntervalId 를 이용해 clearInterval 을 하여 스톱워치를 멈추게 하도록 handleStop 메소드를 선언해주었다.
하지만 이미지를 보면 작동 안하는 모습을 볼 수 있다.
그 이유는 값의 할당은 re-render 이후의 다른 지역환경에서 일어나기 때문이다.

IntervalId 의 값은 re-render 이후 어떤 id 값을 갖는 값으로 변경되지만
컴포넌트는 setInterval 에 의해 지속적으로 setNow 를 호출, 컴포넌트를 호출하며 계속하여 매번 다른 지역 환경을 만들어낸다.
해당 지역 환경에서는 IntervalId 의 값은 여전히 undefined 이다.
컴포넌트가 재호출되어 지역환경이 달라졌기 때문이다.
handleStart이벤트가 호출된 초기의 지역환경에서의IntervalId값은id값이 지정되었을 것이다.
그럼 state 로 설정해두면 좀 괜찮을까 ?
useState로 선언했을 때
state 로 설정하면 작동은 잘 한다.
다만 state 란 시시각각 변하는 변수들을 담기 위한 Hooks 인데 변하지 않는 값을 state Hooks 를 이용하려고 하니
어색하고 목적에 맞지 않는다.
또한 useRef 값이 변할 때 마다 리렌더링이 되는 것을 방지하기 위해 useRef 가 존재한다고 하였으니 useRef 를 써보자
useRef 로 선언했을 때이전 변수 선언문인 let 으로 선언했을 때 문제가 발생했던 이유를 생각해보자
handleStop 내에서 선언된 지역변수 intervalId 의 경우 값이 변하더라도 (let)
상태가 변한 후의 값을 할당받는게 아니라 상태가 변하기 전의 값을 받고 있는 것이 문제였다.

useRef 를 이용하면 컴포넌트가 재호출되어 새로운 지역환경이 되더라도
값이 가장 마지막으로 변경된 시점의 값을 current 프로퍼티에 담고 있기 때문에 잘 작동한다.
useRef vs useState| useRef | useState | |
|---|---|---|
| Returns | { current: initialValue } | [value, setValue] |
| Re-render | Doesn’t trigger re-render when you change it. | Triggers re-render when you change it. |
| Mutability | Mutable—you can modify and update current’s value outside of the rendering process. | ”Immutable”—you must use the state setting function to modify state variables to queue a re-render. |
| Rendering | You shouldn’t read (or write) the current value during rendering. | You can read state at any time. However, each render has its own snapshot of state which does not change. |
useState 와의 차이를 보면 useRef 는 객체 형태를 반환하면서
re-render 를 트리거 하지 않는다는 점에서 useState 와 차이가 존재한다.
또한 useRef 는 따로 setterFunc 없이 직접적으로 변경이 가능한데 이는
렌더링 프로세스와 독립적으로 변경이 가능하다는 점이 useState 와 큰 차이점이다.
useState 는 렌더링 이후에 값이 변경되도록 설정되어있다.
하지만 useRef 의 경우에는 렌더링 프로세스와는 별개로 작동한다.
이로 인해 useState 의 경우에는 렌더링 되는 화면과 현재의 state 값이 동기화되지만
값이 정확히 같지는 않다. 이전에 말했듯
state는 렌더링 이후 값이 업데이트 된다.
useRef 의 값의 경우에는 렌더링 되는 화면과 관련없이 변경이 가능하다.
이는 useState 는 항상 값이 변경되면 렌더링을 일으키지만, useRef 는 그렇지 않다는 점에서 기인한다.
useRef 는 어떤 원리로 동작할까

리액트에서는 더욱 최적화된 형태로 빌트인 되어 있지만 공식문서에서는 다음처럼 보아도 이해 할 수 있을 것이라고 한다.
useRef 의 경우 useState 를 이용해 ref 객체를 생성하고 setter function 은 사용하지 않은 채 state 값을 변경시키도록 한다.
이를 통해 렌더링 프로세스와 독립적으로 변경 가능하게 만들어둔다.
또한 useState 의 로직을 통해 만들어졌기 때문에 컴포넌트가 새롭게 호출되더라도
useState 의 특성으로 인해 useRefCustom 지역변수 내에서 반환되는 ref 객체는 이전에 생성해둔 ref 객체를 받기 때문에 유지가 가능하다.
useRef 를 쓰는게 좋을까 ?useRef 는 주로 컴포넌트 렌더링 로직과 상관 없는 변수들을 담는데 이용하는 것이 좋다.
when your component needs to "step outside"
리액트 공식문서에서는 컴포넌트와 관련 없는 것들을 useRef 를 이용하기를 권장한다.
그 이유는 렌더링 로직과 독립적으로 이동하여 값이 변경되더라도 렌더링을 요구하지 않으며
리렌더링 되어 새롭게 컴포넌트가 호출되더라도 값을 저장 할 수 있는 것은 useRef 이기 때문이다.
전역 변수에 선언해두어도 렌더링 로직과 별개로 사용 할 수 있지만
React를 쓰는 동안에는React Hook을 이용하는 것이 성능상이나 가독성 상에서 좋을 것이다.
다음과 같은 상황에서 자주 사용한다고 한다.
Storing timeoutIDsetTimeout , setInterval 의 id 들은 호출환경이 달라지면 값을 추적 할 수 없지만 ref 를 이용하여 값을 저장하는게 가능했다. Stroing and manipulating DOM ELEMENTDOM 객체를 useRef 에 저장해둬 다음 페이지러 넘어가는 등의 행위를 할 수 있다고 한다. 일종의 노드 캐싱일까 ? 이 부분은 더 챕터가 나아가면 공부 할 수 있을 것이라 생각한다.Storing other objects that aren’t necessary to calculate the JSX.useRef 로 하도록 하자 useRef 를 잘 이용하기 위한 실전 팁useRef 를 escape hatch 처럼 사용해라useRef 는 step outside 에 접근 할 수 있도록 도와주는 hook 이라고 했다.
React 의 로직이 아닌, 다른 시스템이나 브라우저의 API 를 이용하고자 할 때
useRef 를 적절하게 이용하면 외부 환경의 기능들과 React 의 기능들을 조합해 적절하게 사용 할 수 있을 것이다.
useRef 는 값의 변화를 추적하는데 어려움이 있다useState 의 경우에는 값이 변하면 바뀐 값에 따라 렌더링이 되기 때문에
값의 변화를 추적하는게 쉽다.
하지만 useRef 는 값이 변하더라도 렌더링이 되지 않기 때문에 값의 변화를 추적하는 것이 어렵다.
이에 값의 변화를 엄밀하게 추적해야 하는 경우에는 useState 를 이용해주도록 하자
The only exception to this is code like if (!ref.current) ref.current = new Thing() which only sets the ref once during the first render.
유일한 예외 사항은 처음 렌더링 될 때 값이 존재하는지를 확인하고 없다면 값을 재할당 하는 경우이다.