React.useRef()는 DOM element에 접근하는 것 뿐만 아니라,
리렌더링 시에도 지속적으로 유지되는 가변값(reference, 변수라고 생각하면 됨.)을
만들기 위해 사용한다.
1. Mutable values
useRef(initialValue)은 intialValue를 인자로 받고,
reference
를 리턴한다. 이
reference
는 object인데, current
라는 특별한 프로퍼티를 가진다.import { useRef } from 'react'; function MyComponent() { const reference = useRef(initialValue); const someHandler = () => { // Access reference value: const value = reference.current; // Update reference value: reference.current = newValue; }; // ... }
refernce.current
는 reference value에 access한다. 그리고
reference.current = newValue
라는 표현식으로 이 reference value 를 업데이트 한다. 
이 reference 에 대해서 기억해야 할 2가지 규칙이 있다.
- referenceValue는 컴포넌트가 다시 렌더링해도,
persisted
하다. (변하지 않는다)
- reference를 업데이트하는 것은 컴포넌트를 리렌더링하게 하지 않는다.
1.1 Use Case : logging button clicks
LogButtonCliks
컴포넌트는 버튼이 클릭된 횟수를 저장하기 위하여 refernece를 사용한다.import { useRef } from 'react'; function LogButtonClicks() { const countRef = useRef(0); const handle = () => { countRef.current++; console.log(`Clicked ${countRef.current} times`); }; console.log('I rendered!'); return <button onClick={handle}>Click me</button>; }
const countRef = useRef(0)
은 0으로 초기화된(initialzed) countRef
라는 reference를 만들어내는 코드이다. 버튼이 클릭되면,
handle
함수가 평가되며, 이 reference value가 증가한다. : countRef.current++
이 refrence value는 콘솔에 출력(log)된다.refrence value를 업데이트하는
countRef.current++
에서 컴포넌트가 다시 렌더링 되지 않는다. 이는 콘솔을 확인하면 입증된다. (I rendered!
가 처음 렌더링될 때 한번만 출력되고, refernece가 업데이트 될 때는 렌더링되지 않는다.) 그렇다면 생각해볼만한 것이 있다. : reference와 state의 가장 큰 차이점이 뭘까?
Reference and state diff
LogButtonClicks
컴포넌트를 다시 사용해보자. 하지만 이번에는 useStae()
를 사용하여 버튼이 클릭된 횟수를 구해보자.import { useState } from 'react'; function LogButtonClicks() { const [count, setCount] = useState(0); const handle = () => { const updatedCount = count + 1; console.log(`Clicked ${updatedCount} times`); setCount(updatedCount); }; console.log('I rendered!'); return <button onClick={handle}>Click me</button>; }
위의 코드를 실행해보면, 버튼을 클릭할 때 마다 콘솔에
I rendered!
라는 메시지가 출력되는 것을 확인할 수 있다. - 즉, state가 update 될 때 마다 컴포넌트가 다시 렌더링 된다는 의미이다.따라서 reference와 state의 두 가지 차이점이 있다.
- reference를 update 하면 컴포넌트가 다시 렌더링 되지 않는다. 하지만, state가 update되면 다시 렌더링 된다.
- reference의 update는 동기적(synchronous)이다. (update된 reference value는 즉시 사용할 수 있다) 반면 state의 update는 비동기적(asynchronous)이다. (state variable은 컴포넌트가 다시 렌더링 된 후 업데이트 된다.)
더 높은 관점에서 바라보면, reference는 부수효과의 기반적인(infrasturcture) 데이터를 저장한다. 반면 state는 화면에 바로 렌더링 되는 정보를 저장한다. (원문 : From a higher point of view, references store infrastructure data of side-effects, while the state stores information that is directly rendered on the screen. )
즉, reference는 컴포넌트가 아니라 컴포넌트를 위한 로직을 위하여 사용되는 id와 같은 값(이 글에서는 어떤 로직을 위한 기반적인 데이터라고 표현됨)을 저장하는데 주로 사용된다.
1.2 Use case : implementing a stopwatch
따라서 reference는 부수 효과(side effect)의 기반(infrastructre) 데이터를 저장할 수 있다. 예를 들어서 timer ids, socket ids 등등을 reference에 저장할 수 있다.
Stopwatch
컴포넌트는 매 초마다 스톱워치의 초를 증가시키기 위하여 setInterval(callback, tile)
라는 타이머 함수를 사용한다. timer id가 timerIdRef
라는 refernce에 담기게 된다.import { useRef, useState, useEffect } from 'react'; function Stopwatch() { const timerIdRef = useRef(0); const [count, setCount] = useState(0); const startHandler = () => { if (timerIdRef.current) { return; } timerIdRef.current = setInterval(() => setCount(c => c+1), 1000); }; const stopHandler = () => { clearInterval(timerIdRef.current); timerIdRef.current = 0; }; useEffect(() => { return () => clearInterval(timerIdRef.current); }, []); return ( <div> <div>Timer: {count}s</div> <div> <button onClick={startHandler}>Start</button> <button onClick={stopHandler}>Stop</button> </div> </div> ); }
startHandler
함수는 Start 버튼이 클릭되면 평가되어, timer를 시작하고, reference인 timerIdRef에 timer id를 저장하게 된다. ( timerIdRef.current = setInterval(...)
)스톱워치를 멈추기 위하여 Stop 버튼을 누르게 되면,
stopHandler()
가 reference의 timer id에 접근(access)하게 되고, clearInterval(timerIdRef.current)
를 통하여 타이머를 멈추게 된다.추가적으로, 컴포넌트가 이 스톱워치가 활성화 된 상태로 unmount되면
useEffect()
의 cleanup function이 실행되어 타이머를 멈출 것이다.이 스톱워치 예제에서, reference는 active timer id와 같은 기반적인 데이터를 저장할 때 사용되었다.
2. Accessing DOM elements
또한
useRef
를 DOM element에 접근하는데도 유용하게 사용할 수 있다. 이는 3개의 step으로 작동한다.- element에 접근하기 위한 reference를 정의한다. (
const elementRef = useRef()
)
- 그 element에
ref
속성을 지정(assign)한다. (<div ref={elementRef></div>
)
- mounting이 되면,
elementRef.current
는 DOM element를 가리키게 된다.
import { useRef, useEffect } from 'react'; function AccessingElement() { const elementRef = useRef(); useEffect(() => { const divElement = elementRef.current; console.log(divElement); // <div>I'm an element</div> }, []); return ( <div ref={elementRef}> I'm an element </div> ); }
2.1 Use case : focusing an input
예를 들면, 컴포넌트가 mount되면, input filed의 focus 하기 위하여 DOM element에 접근해야 할 수도 있다.
이를 위해, input에 대한 reference를 만들고, input 태그에
ref
속성을 만든 reference에 연결하고, mount 된 이 후 요소에서 element.focus()
메서드를 실행시킨다.import { useRef, useEffect } from 'react'; function InputFocus() { const inputRef = useRef(); useEffect(() => { inputRef.current.focus(); }, []); return ( <input ref={inputRef} type="text" /> ); }
const inputRef = useRef()
는 input element를 담기 위한 reference를 만든다.inputRef
는 ref
속성에 assign 된다. : <input ref={inputRef} type="text" />
mounting 된 이후에,
inputRef.current
에는 이 input element가 들어있을 것이다. 이제, inputRef.current.focus()
를 이용하여 input에 focus 되도록 만들 수 있다.Ref is null on initial rendering
첫 렌더링 시에, reference는 가지고 있는 DOM element는 비어있다(empty) 고 가정한다.
import { useRef, useEffect } from 'react'; function InputFocus() { const inputRef = useRef(); useEffect(() => { // Logs `HTMLInputElement` console.log(inputRef.current); inputRef.current.focus(); }, []); // Logs `undefined` during initial rendering console.log(inputRef.current); return <input ref={inputRef} type="text" />; }

첫 렌더링 시에, 아직 DOM 구조가 만들어지지 않았을 때도, React는 함수 컴포넌트의 output을 결정한다. 따라서
inputRef.current
가 첫 렌더링 시 undefined
로 평가된다.useEffect(callback, [])
hook은 mounting 된 직후(input 요소가 DOM에 만들어진 이 후)에 콜백으로 들어온 함수를 평가한다.따라서,
useEffect
는 DOM이 만들어졌음을 보장하므로, useEffect(callback, [])
의 callback
함수는 inputRef.current
에 올바르게 접근한다.3. Updating reference restriction
함수형 컴포넌트의 함수 스코프 또한 결과값을 평가하거나 hook을 호출한다. (즉 함수형 컴포넌트는 jsx를 반환하는 함수 이므로, 안쪽에 있는 표현식이 실행되게 된다)
따라서, reference를 update하는게 함수 컴포넌트의 중간에서 실행되면 안된다.
reference는
useEffect()
의 callback이나 핸들러에서만 update되어야 한다.import { useRef, useEffect } from 'react'; function MyComponent({ prop }) { const myRef = useRef(0); useEffect(() => { myRef.current++; // Good! setTimeout(() => { myRef.current++; // Good! }, 1000); }, []); const handler = () => { myRef.current++; // Good! }; myRef.current++; // Bad! if (prop) { myRef.current++; // Bad! } return <button onClick={handler}>My button</button>; }
4. Summary
useRef()
는 reference를 생성한다.const reference = useRef(initialValue)
는 reference라는 특별한 객체를 반환한다. reference 객체는 current
라는 프로퍼티를 가진다. : 이 프로퍼티를 통해 reference의 값을 읽거나(reference.current
), reference를 update할 수 있다. (reference.current = newValue
)컴포넌트가 리렌더링될때, reference의 값은 변하지 않는다. (persistent)
reference 를 업데이트 하는 것은 state를 업데이트 하는 것과 다르게,
컴포넌트가 리렌더링되게 하지 않는다.
Referecne를 통하여 DOM 요소에 접근할 수 있으며, 이는 접근하고 싶은 요소에
ref
속성에 reference를 지정하는 방식으로 이루어진다. : <div ref={reference}>Element</div>
(이제 reference.current를 통하여 이 요소를 조작할 수 있다.)