게시글

강좌15 - React - 한 달 전 등록 / 19일 전 수정

React Hooks! useRef편(React 17버전)

이 포스팅들은 오래되었습니다. 유튜브에서 최신 리액트 강좌 강좌를 보시는 것을 추천합니다.

이번 시간에는 useRef hook을 배워봅시다. 여기까지가 자주 쓰이는 훅인 것 같습니다. useContext, useReducer, useImperativeHandle, useLayoutEffect도 리액트의 기본 훅이지만 상대적으로 사용 빈도가 떨어집니다. useContext와 useReducer는 제 유튜브 무료 강좌에서 설명해놓긴 했습니다.

컴포넌트 별로 데이터를 갖고 싶을 때가 있습니다. useState를 쓰면 되지 않냐고요? useState도 데이터를 저장하지만 제가 useState를 설명할 때 한 가지 단서를 달았습니다. 바로 화면 렌더링과 관련된 데이터를 저장하는 공간이라고요. state를 바꾸면 컴포넌트가 리렌더링되고 맙니다. 하지만 만약 화면 리렌더링과는 관련 없는 데이터를 저장하고 싶다면 어떻게 해야 할까요?

직관적으로 떠오르는 방법은 두 개가 있습니다. 컴포넌트 바깥에 데이터를 놓는 것과, 컴포넌트 안에 데이터를 넣는 것이죠.

import React, { useCallback } from 'react';
let data = 0;
const Basic = () => {
  const onClick = useCallback(() => {
    data++;
  }, [data]);
  return <div onClick={onClick}>Basic</div>;
};
export default Basic;

이 방법은 누구나 생각할 법 하지만 한 가지 명심해야할 점이 있습니다. 만약 Basic 컴포넌트를 여러 군데서 사용한다면 그 컴포넌트들이 모두 data라는 데이터를 공유한다는 것입니다.

<Basic />
<Basic />

즉 위과 같은 상황에서 위와 아래의 Basic을 한 번씩 누른다면 data는 2가 되어 있습니다. 컴포넌트 간에 공유하고 싶은 데이터는 이렇게 만드시면 됩니다.

만약 공유하기 싫고 각각 데이터를 갖길 원한다면

import React, { useCallback } from 'react';
const Basic = () => {
  let data = 0;
  const onClick = useCallback(() => {
    data++;
  }, [data]);
  return <div onClick={onClick}>Basic</div>;
};
export default Basic; 

이렇게 안에 쓰는 걸 생각해볼 수도 있을 것입니다. 하지만 이것은 제대로 동작하지 않습니다. 컴포넌트가 리렌더링될 때 let data = 0;이 다시 실행되어 data가 0으로 초기화되어 버리기 때문이죠. 리렌더링과 관련없이 한 번 만들어진 컴포넌트에서 이전 데이터를 유지하고 싶다면 어떻게 해야 할까요? 이 때 useRef 훅이 나옵니다.

import React, { useCallback, useRef } from 'react';
const Basic = () => {
  const dataRef = useRef(0);
  const onClick = useCallback(() => {
    dataRef.current++;
  }, []);
  return <div onClick={onClick}>Basic</div>;
};
export default Basic;

useRef로 생성한 데이터는 리렌더링 여부와 상관없이 같은 값이 유지됩니다. 또한 그 값을 바꾸더라도 화면이 리렌더링되지 않습니다. 초깃값은 useRef로 생성할 때 인수로 넣어주면 됩니다. 여기서 특이한 점은 dataRef의 값을 가져올 때 dataRef.current로 접근해야 한다는 것입니다. dataRef 자체에 대입하면 안 되고 항상 current 속성을 사용해 접근하도록 하세요.

useRef는 class 컴포넌트에서 사용했던 React.createRef와 마찬가지로 DOM 노드를 저장하는 데 사용할 수도 있습니다. ref props로 넣고 나중에 divRef.current.focus()같이 DOM에 직접 접근하면 됩니다.

import React, { useRef } from 'react';
const Basic = () => {
  const divRef = useRef(null);
  return <div ref={divRef}>Basic</div>;
};
export default Basic; 

useRef를 활용한다면 useEffect에서 componentDidUpdate 효과를 낼 수 있습니다. componentDidMount를 무시하는 방법입니다.

import React, { useEffect, useRef } from 'react';
const Basic = () => {
  const mountRef = useRef(false);
  useEffect(() => {
    if (mountRef.current) {
      console.log('updated!');
    } else {
      mountRef.current = true;
    }
  });
  return <div>Basic</div>;
};
export default Basic;  

useEffect는 mount될 때도 한 번 실행되는데 그 때는 mountRef가 false입니다. 따라서 if 문이 동작하지 않습니다. 단, else문에서 mountRef를 true로 바꾸어놨기 때문에 다음 리렌더링때부터는 if문 내부가 실행됩니다.

정리하자면 useRef는 그 안의 데이터가 바뀌어도 화면을 리렌더링하지 않지만, 리렌더링 후에도 데이터를 유지시켜줍니다. 클래스 컴포넌트의 React.createRef에 비해서 활용도도 높습니다.

다음 시간에는 hook의 규칙과 커스텀 훅에 대해 알아보도록 하겠습니다.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

댓글

아직 댓글이 없습니다.