본문 바로가기
SPA/React

[React] Hooks - useRef

by J4J 2021. 9. 13.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 hooks의 useRef에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

useRef란?

 

useRef는 hooks에서 컴포넌트의 전 생명주기 동안 객체 값을 유지해주는 역할을 담당합니다.

 

여기서 값을 유지해준다는 것은 말 그대로 어느곳에서라도 한번 저장된 값을 그대로 유지시켜 준다는 것입니다.

 

 

 

리액트를 이용하여 어느정도 개발해보신 분들이라면 useState나 또는 let을 이용한 일반 변수값을 선언한 뒤 데이터를 업데이트할 때 본인이 생각한 데이터로 업데이트가 되지 않았던 현상을 겪어보셨을 수도 있습니다.

 

예를 들어 setTimeout, setInterval을 사용한다거나 또는 액션함수에서 업데이트한 데이터가 업데이트되지 않고 그대로 유지되는 등의 케이스들이 있습니다.

 

이런 상황들을 해결할 수 있는 방법은 여러가지가 있겠지만 useRef를 사용한다면 보다 현명하게 대응할 수 있을 것이라고 감히 생각해봅니다.

 

 

 

useRef의 사용방법은 간단합니다.

 

기본적으로 useRef는 'react'패키지 내부에 존재하기 때문에 리액트로 개발되는 어느곳에서든 사용할 수가 있습니다.

 

그리고 vscode에서 제공해주는 useRef에 대한 설명을 다음과 같습니다.

 

function React.useRef<number>(initialValue: number): React.MutableRefObject<number> (+2 overloads)

 

 

 

위의 설명을 해석해보면 파라미터 값으로 초기값을 넣어줄 수 있습니다.

 

현재 타입은 number로 되어있는데 제가 초기값으로 0을 넣어줘서 그런것이고 number뿐만 아니라 어떤 객체 값이든 다 넣어줄 수 있습니다.

 

리턴할 때는 객체를 리턴해주고 해당 값을 변수에 저장하여 여러 장소에서 가져다 사용할 수 있습니다.

 

 

 

여기서 한 가지 짚고 넣어가야 될 점은 useRef에서 데이터를 저장하는 곳은 current입니다.

 

그렇기 때문에 useRef을 선언할 때 리턴 받은 객체를 A에다가 저장했다고 가정하면 저장된 데이터를 가져다 사용하거나 또는 업데이트를 할 때 A.current와 같이 사용해주셔야 합니다.

 

 

 

useRef는 사용목적이 유사한 useState, 일반 변수들과 많이 비교 되고는 합니다.

 

useRef의 특징도 보고 비교도 해보기 위해 간단한 예시 코드를 만들어 보겠습니다.

 

 

 

예시 코드 1 - 이벤트 처리

 

동작 방식이 다음과 같이 될 수 있도록 예시 코드를 작성해보겠습니다.

 

  • Toggle버튼을 클릭하면 값 증가 버튼이 있는 컴포넌트를 그리기
  • 증가 버튼을 누르면 useState, useRef, 일반 변수 데이터를 업데이트
  • Toggle버튼을 다시 클릭하면 컴포넌트가 화면에서 제거

 

 

반응형

 

 

저는 타입 설정도 보여드리기 위해 타입 스크립트를 이용하여 코드를 작성할 거여서 App.tsx파일에 다음과 같이 작성해보겠습니다.

 

import * as React from 'react';
import AddComponent from './addComponent';

const App = (): JSX.Element => {

    /* 타입 스크립트 사용할 경우 */
    const [toggle, setToggle] = React.useState<boolean>(false);

    /* 타입 스크립트 사용하지 않을 경우 
    const [toggle, setToggle] = React.useState(false);
    */

    return (
        <>
            <div style={{ marginBottom: '20px' }}>
                <button onClick={() => setToggle(!toggle)}>Toggle</button>
            </div>
            {toggle ? <AddComponent /> : ''}
        </>
    )
}

export default App;

 

 

 

그리고 addComponet.tsx는 다음과 같이 작성해보겠습니다.

 

import * as React from 'react';

const AddComponent = ():JSX.Element => {
    
    /* 타입 스크립트 사용할 경우 */
    const [stateVal, setStateVal] = React.useState<number>(0);
    const refVal = React.useRef<number>(0);
    let variableVal: number = 0;

    /* 타입 스크립트 사용하지 않을 경우 
    const [stateVal, setStateVal] = React.useState(0);
    const refVal = React.useRef(0);
    let variableVal: number = 0;
    */

    React.useEffect(() => {
        return () => {
            console.log(`stateVal: ${stateVal}`);
            console.log(`refVal: ${refVal.current}`);
            console.log(`variableVal: ${variableVal}`);
        }
    }, [])

    const add = () => {
        console.log(`stateVal: ${stateVal}`);
        console.log(`refVal: ${refVal.current}`);
        console.log(`variableVal: ${variableVal}`);
        
        setStateVal(stateVal+1);
        refVal.current = refVal.current+1;
        variableVal = variableVal+1;
    }

    return (
        <button onClick={add}>증가</button>
    )
}

export default AddComponent;

 

 

 

위의 코드를 작성하여 실행해보면 다음과 같은 결과가 나옵니다.

 

실행 결과

 

 

 

증가버튼을 클릭할 때는 다음과 같은 상황들이 발생합니다.

 

클릭 이벤트가 발생될 때 add함수가 실행되는데 add함수가 실행될 때마다 variableVal 변수는 매번 기존에 0으로 저장되어 있는 값을 바라보기 때문에 항상 0이 콘솔에 출력됩니다.

 

이와 달리 stateVal은 리렌더링이 발생되었고, refVal은 객체 값이 유지되기 때문에 값이 지속적으로 증가되는 것을 볼 수 있습니다.

 

 

728x90

 

 

컴포넌트가 제거될 때는 다음과 같은 상황들이 발생합니다.

 

stateVal은 useEffect의 두 번째 파라미터값으로 아무것도 넣어주지 않았기 때문에 값이 증가하더라도 리렌더링이 발생되지 않아 처음 마운트 될 때 저장되어 있던 0이 콘솔에 출력됩니다.

 

variableVal은 add함수를 통해 값이 1만큼 증가된 상태로 유지되어 있기 때문에 1이 콘솔에 출력됩니다.

 

refVal은 add함수를 통해 증가되던 값이 그대로 유지되어 있기 때문에 증가한만큼 콘솔에 출력됩니다.

 

 

 

예시 코드 2 - setInterval

 

동작 방식이 다음과 같이 출력될 수 있도록 예시 코드를 작성해보겠습니다.

 

  • Toggle버튼을 클릭하면 setInterval이 동작되는 컴포넌트를 그리기
  • 1초마다 setInterval이 동작되어 값들을 지속적으로 증가
  • Toggle버튼을 다시 클릭하면 컴포넌트가 화면에서 제거

 

 

 

위와 같이 동작뒤기 위해 App.tsx를 다음과 같이 수정하겠습니다.

 

import * as React from 'react';
import IntervalComponent from './intervalComponent';

const App = (): JSX.Element => {

    /* 타입 스크립트 사용할 경우 */
    const [toggle, setToggle] = React.useState<boolean>(false);

    /* 타입 스크립트 사용하지 않을 경우 
    const [toggle, setToggle] = React.useState(false);
    */

    return (
        <>
            <div style={{ marginBottom: '20px' }}>
                <button onClick={() => setToggle(!toggle)}>Toggle</button>
            </div>
            {toggle ? <IntervalComponent /> : ''}
        </>
    )
}

export default App;

 

 

 

그리고 intervalComponent.tsx를 생성하여 다음과 같이 작성하겠습니다.

 

import * as React from 'react';

const IntervalComponent = ():JSX.Element => {
    
    /* 타입 스크립트 사용할 경우 */
    const [stateVal, setStateVal] = React.useState<number>(0);
    const refVal = React.useRef<number>(0);
    let variableVal: number = 0;

    /* 타입 스크립트 사용하지 않을 경우 
    const [stateVal, setStateVal] = React.useState(0);
    const refVal = React.useRef(0);
    let variableVal: number = 0;
    */

    React.useEffect(() => {
        const interval = setInterval(() => {
            console.log(`stateVal: ${stateVal}`);
            console.log(`refVal: ${refVal.current}`);
            console.log(`variableVal: ${variableVal}`);
            
            setStateVal(stateVal+1);
            refVal.current = refVal.current+1;
            variableVal = variableVal + 1;
        }, 1000);

        return () => {
            clearInterval(interval);
            console.log(`stateVal: ${stateVal}`);
            console.log(`refVal: ${refVal.current}`);
            console.log(`variableVal: ${variableVal}`);
        }
    }, [])

    return (
        <h4>Interval Component ...</h4>
    )
}

export default IntervalComponent;

 

 

 

위와 같이 코드를 작성하고 실행해보면 다음과 같은 결과를 볼 수 있습니다.

 

실행 결과

 

 

 

컴포넌트가 마운트되고나서는 다음과 같은 상황이 발생합니다.

 

stateVal은 실행된 함수가 모두 종료되지 않았기 때문에 변경된 값도 적용되지 않아 콘솔 창에 0이 출력됩니다.

 

variableVal은 바라보고 있는 값이 동일하기 때문에 값이 계속 증가되고 refVal도 증가된 값들이 유지되기 때문에 계속 값이 증가합니다.

 

 

 

컴포넌트가 제거될 때에도 동일한 상황들이 동일한 이유로 발생합니다.

 

 

 

 

예시 코드 3 - DOM요소 접근

 

사실 대부분의 리액트를 개발하시는 분들은 useRef를 dom요소에 접근하기 위해 사용하실 겁니다.

 

물론 useState를 사용해서도 dom요소에 접근할 수 있지만 기본적으로 useRef는 리렌더링이 발생되지 않고 값을 유지하기 때문에 useState보다 효율적인 dom제어를 위한 방식이지 않나 생각합니다.

 

간단한 코드를 작성해보면 다음과 같이 div요소에 접근하여 현재 높이를 구해볼 수 있습니다.

 

import * as React from 'react';

const App = (): JSX.Element => {

    const divEl = React.useRef<HTMLDivElement>(null);

    React.useEffect(() => {
        console.log(divEl.current && divEl.current.clientHeight);
    }, [])

    return (
        <div ref={divEl}>
            <h4>App.tsx file ...</h4>
        </div>
    )
}

export default App;

 

 

 

참조

 

[React] useRef 200% 활용하기

useRef vs variable, useState 차이점

공식 문서

 

 

 

 

이상으로 hooks의 useRef에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

'SPA > React' 카테고리의 다른 글

[React] Hooks - useMemo  (0) 2021.09.22
[React] Hooks - useCallback  (0) 2021.09.15
[React] Hooks - useState  (0) 2021.09.05
[React] Hooks - useEffect  (0) 2021.09.02
[React] StyledComponents Tree Shaking 적용  (0) 2021.09.01

댓글