최근에 타이머를 만들 일이 생겼습니다.
setInterval()을 이용해 주기적으로 특정 함수를 호출할 수 있기 때문에 타이머를 구현할 때는 보통 이 함수를 사용합니다.
const [time, setTime] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(time);
setTime(time + 1);
}, 1000);
}, [])
처음에는 위와 같이 구현해봤습니다.
매초마다 time 값이 1씩 증가하는 것을 예상했으나 동작이 제대로 되지 않았습니다.
화면에는 0에서 1로 증가하더니 더이상 증가하지 않았고, 로그에는 계속 0만 찍히는 모습입니다.
그래서 코드를 아래와 같이 바꿔보았습니다. (setLocalTime에 함수를 전달)
const [time, setTime] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(time);
setTime(t => t + 1);
}, 1000);
}, [])
그랬더니 화면상에서는 숫자가 제대로 올라가는 걸 확인할 수 있었습니다.
그러나 로그에는 이전과 똑같이 0만 계속해서 출력되었습니다.
왜 이런 이상한 일이 일어나는 걸까?
그것은 우리가 함수형 컴포넌트를 사용하기 때문입니다.
더 구체적으로는, 함수형 컴포넌트는 곧 클로저를 만들기 때문입니다.
함수형 컴포넌트에서, state가 변하면 리액트는 함수를 재실행합니다.
자바스크립트의 모든 함수는 클로저이므로 그 함수가 실행될 때의 함수 내부의 값을 그대로 가지고 있습니다.
이는 위에서 time 값이 0에서 고정된 채로 더이상 바뀌지 않는다는 것을 의미합니다.
( 리액트가 함수형 컴포넌트를 호출(렌더링)할 때마다 클로저가 생성됩니다! )
useState()로부터 끌어낸 state인 time이 마법처럼 실제 상태와 동기화되어 있다고 생각할 수 있는데,
정확히는 time은 그저 클로저 내부에 갇혀있는 상수일 뿐이며 state의 데이터 흐름을 사진찍듯이 찰칵 찍어 놓은 것에 불과합니다.
따라서 setLocalTime(time + 1); 은 곧 setLocalTime(1); 과 완전히 같은 코드가 됩니다.
하지만 setLocalTime(t => t + 1); 과 같이 함수를 넘겨주는 경우, 우리는 리액트에게 "이전 상태에다 1을 더하렴" 이라고 명령을 내려주는 효과를 얻습니다. 이 명령에서는 time을 전혀 사용하지 않으므로, 클로저 문제에서 벗어나게 됩니다.
그렇다면 과거의 클로저에서는, 최신 state를 가져올 순 없는 걸까?
가능합니다. 과거의 렌더링이 만들어낸 클로저는, 미래에 실행될 렌더링의 "현재" 값을 살펴봐야 합니다.
마치 타임머신을 탄 것처럼 우리는 클로저에 종속되어 있지 않은, 초월한 무언가를 이용해야 합니다.
그게 바로 useRef() 라는 훅입니다.
아래 코드를 봅시다.
const [time, setTime] = useState(0);
const myRef = useRef(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(myRef.current);
setTime(state => state + 1);
}, 1000);
}, [])
useEffect(() => {
myRef.current = time;
})
여기서 리액트 개발자들의 키워드 선정을 느껴볼 수 있습니다.
두 번째 useEffect(현재)에서 myRef의 current 값을 time으로 설정합니다.
첫 번째 useEffect(과거)에서는 myRef의 current 값을 출력합니다.
그렇습니다. 우리는 클로저가 생성된 시점에 관계없이, 업데이트된 "현재" 값을 볼 수 있어야 했고, 그것은 useRef의 current(현재) 속성을 가져옴으로써 가능해집니다.
따라서 console.log() 에서 과거에 갇힌 time을 출력하는 대신 현재에 충실한 myRef.current 를 출력하고,
콘솔창에는 정상적으로 0, 1, 2, 3... 과 같이 숫자들이 출력되게 됩니다.
'React' 카테고리의 다른 글
[React] React.FC, React.VFC를 쓰면 안되는 이유 (0) | 2022.04.04 |
---|---|
[React] 함수형 컴포넌트에서 defaultProps와 default parameters 중 무엇을 사용해야 할까? (0) | 2022.04.03 |
[React] props에 객체를 전달하면: pass by reference (0) | 2022.02.24 |
[React] 리액트 디자인 패턴에 대한 고찰 (0) | 2022.02.22 |
[React Native] axios로 formData 전송이 안되는 문제 (0) | 2022.02.22 |