혼자 나름대로 공부한 내용을 바탕으로만 개발하다가, 이번에 면접을 준비하면서 좀 더 깊은 공부를 하고 싶다는 생각에 본 200% 활용하기 포스팅을 작성하게 되었습니다!
최근에 면접 질문으로 아래와 같은 질문을 받았습니다.
React에서 Component가 unmount 될 때의 state를 딱 한번만 log를 찍고 싶은데, 그러려면 어떻게 구현하면 될까요?
우선 unmount 시점을 인식할 수 있는 건 hook에선 useEffect
의 clean-up function
을 활용할 수 있습니다.
const Conter = () =>{
const [count, setCount] = useState(0);
useEffect(() => {
return () => {
console.log("unmount 시 출력", count);
};
}, []);
return (
<>
<h1>{count}</h1>
<button onClick={()=>setCount(c=>c+1)}>+<button/>
</>
}
useEffect
함수 내부에 return
키워드를 사용하여 return
의 스코프 안에 정리(clean-up)
하고자 하는 코드를 작성하여 메모리 누수를 방지할 수 있습니다.
처음 대답했던 코드는 다음과 같은 방식이었습니다. useEffect
의 dependency
배열을 비워두고, clean-up 부분에 console.log
를 찍으면 될 것이라고 예측했으나 해당 코드는 처음 렌더링 될 때의 state
를 출력하게 됩니다.
무슨 뜻 이냐면, 해당 코드에서 button을 눌러서 count
state를 증가 시키더라도, 해당 컴포넌트가 unmount 될 때의 state
의 값인 5
가 아닌, 초기 state
의 값인 0
이 출력되게 됩니다.
또 생각해 볼 수 있는 실수론, 다음과 같은 경우가 있겠습니다.
useEffect
의 dependency
배열에 count
라는 state를 추가해 주는 경우 입니다.
const Conter = () =>{
const [count, setCount] = useState(0);
useEffect(() => {
return () => {
console.log("unmount 시 출력", count);
};
}, [count]);
return (
<>
<h1>{count}</h1>
<button onClick={()=>setCount(c=>c+1)}>+<button/>
</>
}
하지만 예상하셨듯이, state
가 re-render 될 때 마다 console.log
가 찍히게 됩니다.
원래의 의도는 unmount 될 때의 state
만 궁금했는데 말이죠.
위의 두 case에서 얻은 교훈은, 우리가 원하는 결과를 얻기 위해선 React의 re-render에 독립적인 state를 사용해야 된다는 것을 알 수 있습니다. useEffect
의 clean-up function 안에 console.log('unmount 시의 count:', count)
를 넣어야 하므로 React의 생명주기에 의존한 state
가 같이 출력될 것입니다.
이를 해결하기 위한 방법으로 useRef hook
을 사용할 수 있습니다!
React 공식문서를 통해 useRef
의 정의를 살펴보면,
useRef
는.current
프로퍼티로 전달된 인자로 초기화된 변경 가능한 ref 객체를 반환합니다. 반환된 객체는 컴포넌트의 전 생애주기를 통해 유지될 것입니다.useRef
는.current
프로퍼티에 변경 가능한 값을 담고 있는 "상자"와 같습니다.- 그리고
.current
프로퍼티를 변경하더라도 이 것이 리렌더링을 발생시키진 않습니다.
좀 더 쉽게 말하자면, ref
는 는 변경 가능한 값을 담고 있는 상자로서, 객체입니다. 이때 ref
객체 안의 값은 React의 생명주기에 독립적 즉, re-render와 상관 없이 유지되는 값 입니다. 값이 변경되더라도 render를 발생시키지 않고, render 되더라도 값이 유지되는 특징을 가집니다!
이 때 ref를 클래스형 컴포넌트에선 React.createRef()
함수를 사용해서 ref 객체를 생성하지만, 우리가 주로 사용하는 함수형 컴포넌트에선 useRef()
hook을 사용하게 됩니다.
자, 그럼 원래대로 돌아가서 ref
를 통해 문제를 해결해 보겠습니다.
import React, { useEffect, useState, useRef } from 'react';
const Conter = () =>{
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
},[count]) // (1) 첫 번째 useEffect
useEffect(() => {
return () => {
console.log("unmount 시 출력", countRef.current);
};
}, []); // (2) 두 번째 useEffect
return (
<>
<h1>{count}</h1>
<button onClick={()=>setCount(c=>c+1)}>+<button/>
</>
}
앞선 설명에서 useRef를 통해 만든 ref
객체 내부의 값은, render와 상관 없이 유지 된다고 배웠습니다. 초기에 const countRef = useRef(count);
를 통해서 countRef 객체에 count 라는 state를 할당해 주었는데,(이때 값은 countRef.current
에 저장됩니다.) state가 변하더라도 render에 상관없이 유지되므로 state
가 바뀌어도 ref
안의 값은 0이 유지됩니다.
따라서 첫 번째 useEffect의 의존성 배열 안에 count state를 넣어 줌으로써, componentDidUpdate()
의 역할을 하게 하여 count
state가 바뀔 때 마다 countRef
의 값을 count로 업데이트 해 주도록 했습니다.
이후에 두 번째 useEffect
에서 의존성 배열을 비워두고 clean-up 함수로써 작성하여 countRef.current
의 값을 출력하면, unmount 직전의 count
state 값을 출력할 수 있게 됩니다.
re-render와 상관없이 값이 유지되는 ref를 통해 문제를 해결했습니다.
실제로 어떤 경우에 사용되는지 알아보겠습니다!
const InputRef = () => {
const inputRef = useRef(null);
const onFocus = () => {
inputRef.current.focus();
};
return (
<>
<input type="text" ref={inputRef} />
<button onClick={onFocus}>inputFocus</button>
</>
);
}
useRef()
로 생성한 ref
는 객체라고 배웠습니다. 이 객체에 일반 변수 뿐만 아니라, DOM
을 담을 수도 있습니다. <input ref={inputRef} />
와 같이 html 태그의 ref 속성에 우리가 만든 inputRef
라는 객체를 할당 해주게 되면, inputRef.current
에 input 태그에 해당하는 DOM이 담기게 됩니다.(DOM 또한 객체입니다!)
따라서 위의 코드에서처럼, button을 클릭한 경우에 input 박스에 포커싱이 되도록 DOM에 접근하여 focus()
함수를 실행시켜 구현할 수 있습니다.
이번 포스팅에선 ref의 개념과, React의 함수형 컴포넌트에서 useRef hook을 이용하여 ref를 구현하는 방법을 알아보았습니다! ref는 결국 render와 상관없이 전역변수의 개념이라고 이해하셔도 좋을 것 같습니다. 이후엔 useMemo와 useCallback 훅에 대해서 알아보겠습니다. 감사합니다 😁
참고로, let과 const var를 이용한 변수를 선언하면 render시에도 값이 유지되지 않는데, 이는 아래의 reference의 useRef vs variable 포스팅을 참고해주시면 좋을 것 같습니다 〰️
리액트 공식문서 : https://ko.reactjs.org/docs/hooks-reference.html#useref
Understanding React's useRef Hook : https://ui.dev/useref/
벨로퍼트 모던리액트(useRef) : https://react.vlpt.us/basic/10-useRef.htmzl
useRef vs variable : https://velog.io/@pks787/useRef-vs-variable-useState-%EC%B0%A8%EC%9D%B4%EC%A0%90
최근 들어서 제대로 이해하게 된 useRef를 다시한번 확인하게 됬습니다! 좋은글 감사합니다!