[React] useRef 200% 활용하기

Juno·2021년 4월 3일
72
post-thumbnail
post-custom-banner

🚘 들어가기

혼자 나름대로 공부한 내용을 바탕으로만 개발하다가, 이번에 면접을 준비하면서 좀 더 깊은 공부를 하고 싶다는 생각에 본 200% 활용하기 포스팅을 작성하게 되었습니다!

최근에 면접 질문으로 아래와 같은 질문을 받았습니다.

React에서 Component가 unmount 될 때의 state를 딱 한번만 log를 찍고 싶은데, 그러려면 어떻게 구현하면 될까요?

우선 unmount 시점을 인식할 수 있는 건 hook에선 useEffectclean-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)하고자 하는 코드를 작성하여 메모리 누수를 방지할 수 있습니다.

처음 대답했던 코드는 다음과 같은 방식이었습니다. useEffectdependency 배열을 비워두고, clean-up 부분에 console.log를 찍으면 될 것이라고 예측했으나 해당 코드는 처음 렌더링 될 때의 state를 출력하게 됩니다.

무슨 뜻 이냐면, 해당 코드에서 button을 눌러서 count state를 증가 시키더라도, 해당 컴포넌트가 unmount 될 때의 state의 값인 5가 아닌, 초기 state의 값인 0 이 출력되게 됩니다.

또 생각해 볼 수 있는 실수론, 다음과 같은 경우가 있겠습니다.
useEffectdependency 배열에 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만 궁금했는데 말이죠.

💡 useRef를 사용하자!

 위의 두 case에서 얻은 교훈은, 우리가 원하는 결과를 얻기 위해선 React의 re-render에 독립적인 state를 사용해야 된다는 것을 알 수 있습니다. useEffect의 clean-up function 안에 console.log('unmount 시의 count:', count) 를 넣어야 하므로 React의 생명주기에 의존한 state가 같이 출력될 것입니다.

이를 해결하기 위한 방법으로 useRef hook을 사용할 수 있습니다!

React 공식문서를 통해 useRef의 정의를 살펴보면,

  1. useRef.current 프로퍼티로 전달된 인자로 초기화된 변경 가능한 ref 객체를 반환합니다. 반환된 객체는 컴포넌트의 전 생애주기를 통해 유지될 것입니다.
  2. useRef.current 프로퍼티에 변경 가능한 값을 담고 있는 "상자"와 같습니다.
  3. 그리고 .current 프로퍼티를 변경하더라도 이 것이 리렌더링을 발생시키진 않습니다.

 좀 더 쉽게 말하자면, ref는 는 변경 가능한 값을 담고 있는 상자로서, 객체입니다. 이때 ref 객체 안의 값은 React의 생명주기에 독립적 즉, re-render와 상관 없이 유지되는 값 입니다. 값이 변경되더라도 render를 발생시키지 않고, render 되더라도 값이 유지되는 특징을 가집니다!

 이 때 ref를 클래스형 컴포넌트에선 React.createRef() 함수를 사용해서 ref 객체를 생성하지만, 우리가 주로 사용하는 함수형 컴포넌트에선 useRef() hook을 사용하게 됩니다.

🙏 React의 re-render와 독립적인 ref

자, 그럼 원래대로 돌아가서 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 값을 출력할 수 있게 됩니다.

😆 ref 활용하기

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 포스팅을 참고해주시면 좋을 것 같습니다 〰️

📋 reference

리액트 공식문서 : 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

profile
사실은 내가 보려고 기록한 것 😆
post-custom-banner

5개의 댓글

comment-user-thumbnail
2021년 4월 13일

최근 들어서 제대로 이해하게 된 useRef를 다시한번 확인하게 됬습니다! 좋은글 감사합니다!

1개의 답글
comment-user-thumbnail
2021년 10월 9일

와 한참 찾았는데 가장 좋은 설명이셨습니다!! 덕분에 잘 이해했습니다 감사합니다 :)

답글 달기
comment-user-thumbnail
2022년 7월 13일

오...첫번째 방법은 클로저 때문인가 보군요

답글 달기