리액트 공식문서 중 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 timeoutID
setTimeout , setInterval
의 id
들은 호출환경이 달라지면 값을 추적 할 수 없지만 ref
를 이용하여 값을 저장하는게 가능했다. Stroing and manipulating DOM ELEMENT
DOM
객체를 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.
유일한 예외 사항은 처음 렌더링 될 때 값이 존재하는지를 확인하고 없다면 값을 재할당 하는 경우이다.