개발공부/REACT

useMemo, useCallback, useRef [처음 만난 리액트 #22]

sohee! 2023. 2. 4. 14:15

7강. Hooks


3. useMemo()

Memorized value를 리턴하는 Hook

 

Memorized value란?

Memoization

최적화를 위해 사용하는 개념

 

비용이 높은, 즉 연산량이 많이 드는 함수의 호출 결과를 저장해두었다가 같은 입력값으로 함수를 호출하면 새로 함수를 호출하지 않고 이전에 저장해놨던 호출 결과를 바로 반환

-> 함수 호출 결과를 받기까지 걸리는 시간이 짧아짐 & 불필요한 중복 연산도 하지 않음 : 컴퓨터의 자원을 적게 사용

 

Memoization된 결과값을 Memorized value라고 부름

 


useMemo() 사용법

const memoizedValue = useMemeo(
    () => {
        // 연산량이 높은 작업을 수행하여 결과를 반환
        return computeExpensiveValue(의존성 변수1, 의존성 변수2);
    },
    [의존성 변수1, 의존성 변수2]
);

 

● useMemo() 훅은 파라미터로 memoizedValue를 생성하는 create 함수의존성 배열을 받음

 

● Memoization의 개념처럼, 의존성 배열에 들어있는 변수가 변했을 경우에만 새로 create 함수를 호출하여 결과값을 반환하며, 그렇지 않은 경우에는 기존 함수의 결과값을 그대로 반환

 

● useMemo() 훅을 사용하면 컴포넌트가 다시 렌더링 될 때 마다 연산량이 높은 작업들을 반복하는 것을 피할 수 있음 

-> 빠른 렌더링 속도

 

 

** useMemo()로 전달된 함수는 렌더링이 일어나는 동안 실행된다는 점을 기억!

렌더링이 일어나는 동안 실행되어서는 안될 작업을 useMemo()의 함수에 넣으면 안됨

 

ex) useEffect() 훅에서 실행되어야 할 side effect

서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 작업들은 렌더링이 일어나는 동안 실행되어서는 안되기 때문에 useMemo() 훅의 함수에 넣으면 안되고 useEffect() 훅을 사용해야 함

 

의존성 배열을 넣지 않을 경우, 매 렌더링마다 함수가 실행됨

const memeoizedValue = useMemo(
    () => computeExpensiveValue(a, b)
);

 

-> useMemo() 훅에 의존성 배열을 넣지 않고 사용하는 것은 아무런 의미가 없음

 

의존성 배열이 빈 배열인 경우,  컴포넌트 마운트 시에만 호출됨

const memoizedValue = useMemo(
    () => {
        return computeExpensiveValue(a, b);
    },
    []
);

 

마운트 이후에는 값이 변경되지 않음 

마운트 시점에만 한 번 값을 계산할 필요가 있을 경우 이렇게 사용할 수 있음

But 대부분의 경우에는 useMemo() 훅의 의존성 배열에 변수들을 넣고, 해당 변수들의 값이 바뀜에 따라 새로 값을 계산해야할 경우 사용함

 

3. useCallback()

useMemo() Hook과 유사하지만 값이 아닌 함수를 반환

컴포넌트가 렌더링될 때 마다 매번 함수를 새로 정의하는 것이 아니라 의존성 배열의 값이 바뀌는 경우에만 함수를 새로 정의해서 리턴

 


useCallback() 사용법

const memoizedCallback = useCallback(
    () => {
        doSomething(의존성 변수1, 의존성 변수2);
    },
    [의존성 변수1, 의존성 변수2]
);

 

 useMemo() 훅과 마찬가지로 함수(Callback)의존성 배열을 파라미터로 받음

 

● 의존성 배열에 있는 변수 중 하나라도 변경되면 Memoization된 Callback 함수를 반환

의존성 배열에 따라 memoized 값을 반환한다는 점은 useMemo() 훅과 동일 

 


동일한 역할을 하는 두 줄의 코드

useCallback(함수, 의존성 배열);

useMemo(() => 함수, 의존성 배열);

 

useCallback() 훅을 사용하지 않고 컴포넌트 내의 함수를 정의한다면, 매번 렌더링이 일어날 때 마다 함수가 새로 정의

-> useCallback() 훅을 사용하여 특정 변수의 값이 변한 경우에만 함수를 다시 정의하도록 해서 불필요한 반복작업을 업새줌

 

<예제코드 - useCallback() 훅을 사용하지 않은 경우>

import { useState } from "react";

function ParentComponent(props) {
    const [count, setCount] = useState(0);

    // 재렌더링 될 때마다 매번 함수가 새로 정의됨
    const handleClick = (event) => {
        // 클릭 이벤트 처리
    };
    
    return (
        <div>
            <button
                onClick={() => {
                    setCount(count + 1);
                }}
            >
                {count}
            </button>
            
            <ChildComponent handleClick={handleClick} />
        </div>
    );
}

 

위 코드처럼 useCallback() 훅을 사용하지 않고 컴포넌트 내에서 정의한 함수를 자식 컴포넌트의 props로 넘겨 사용하는 경우에 부모 컴포넌트가 다시 렌더링 될 때 마다 매 번 자식 컴포넌트도 다시 렌더링 됨

 

<예제코드 - useCallback() 훅을 사용한 경우>

import { useState } from "react";

function ParentComponent(props) {
    const [count, setCount] = useState(0);

    // 컴포넌트가 마운트 될 때만 함수가 정의됨
    const handleClick = useCallback((event) => {
        // 클릭 이벤트 처리
    }, []);
    
    return (
        <div>
            <button
                onClick={() => {
                    setCount(count + 1);
                }}
            >
                {count}
            </button>
            
            <ChildComponent handleClick={handleClick} />
        </div>
    );
}

 

useCallback() 훅을 사용하면 특정 변수의 값이 변한 경우에만 함수를 다시 정의하게 되므로, 

함수가 다시 정의되지 않는 경우에는 자식 컴포넌트도 재렌더링이 일어나지 않음

 

이 경우에는 의존성 배열에 빈 배열이 들어갔기 때문에 컴포넌트가 처음 마운트 되는 시점에만 함수가 정의되고 이후에는 다시 정의되지 않으며, 결국 자식 컴포넌트도 불필요하게 재렌더링이 일어나지 않게 됨

 


3. useRef()

Reference를 사용하기 위한 Hook

 

Reference란?

특정 컴포넌트에 접근할 수 있는 객체

 

useRef() Hook은 Reference 객체를 반환

 

Reference 객체에는 current라는 속성이 있음

current : 현재 참조하고 있는 Element

 


 useRef() 사용법

const refContainer = useRef(초깃값);

 

파라미터로 초깃값을 넣으면 해당 초깃값으로 초기화된 Reference 객체를 반환

만약 초깃값이 null이라면 current의 값이 null인 Reference 객체가 반환됨

 

이렇게 반환된 Reference 객체는 컴포넌트의 lifetime 전체에 걸쳐 유지

(컴포넌트가 마운트 해제 전 까지는 계속 유지)

useRef() Hook은 변경 가능한 current라는 속성을 가진 하나의 상자

 


useRef() Hook 예제코드

function TextInputWithFocusButton(props) {
    const inputElem = useRef(null);
    
    const onButtonClick = () => {
        //`currunt`는 마운트된 input element를 가리킴
        inputElem.current.focus();
    };
    
    return (
        <>
            <input ref={inputElem} type="text" />
            <button onClick={onButtonClick}>
                Focus the input
            </button>
        </>
    );
}

 

▲ useRef() Hook을 사용하여 버튼 클릭 시 input에 focus하도록 하는 코드

 

초깃값으로 null을 넣었고, 결과로 반환된 inputElem이라는 Reference 객체를 input 태그에 넣어줌

버튼 클릭시 호출되는 함수에서 inputElem.current를 통해 실제 element에 접근하여 focus 함수를 호출하고 있음

 

 

<div ref={myRef} />

 

리액트에서는 위와 같이 코드를 작성하면 노드가 변경될 때마다 myRef의 current 속성에 현재 해당되는 DOM 노드를 저장

 

useRef() Hook은 클래스의 인스턴스 필드를 사용하는 것과 유사하게 다양한 변수를 저장할 수 있다는 장점이 있다

- 일반적인 자바스크립트 객체를 리턴하기 때문

 

직접 current 속성이 포함된 자바스크립트 객체를 만들어서 써도 되는 것이 아닌가?

useRef() Hook은 매번 렌더링될 때 마다 항상 같은 Reference 객체를 반환하기 때문에 다름!

 

useRef() Hook은 내부의 데이터가 변경되었을 때 별도로 알리지 않는다

current 속성을 변경한다고 해도 재렌더링이 일어나지는 않음

Ref에 DOM 노드가 연결되거나 분리되었을 경우에 어떤 코드를 실행하고 싶다면 callback ref를 사용해야 함

 


Callback ref

DOM 노드의 변화를 알기 위한 가장 기초적인 방법으로 callback ref를 사용하는 방법

리액트는 ref가 다른 노드에 연결될 때 마다 callback을 호출

 

function MeasureExample(props) {
    const [height, setHeight] = useState(0);
    
    const measuredRef = useCallback(node => {
        if (node !== null) {
            setHeight(node.getBoundingClientReact().height);
        }
    }, []);
    
    return ( 
        <>
            <h1 ref={measuredRef}>안녕, 리액트</h1>
            <h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
        </>
    );
}

 

위 코드에는 Reference를 위해서 useRef() Hook을 사용하지 않고, useCallback() Hook을 사용하는 Callback ref 방식을 사용

 

● useRef() Hook을 사용하게 되면 Reference 객체가 current 속성이 변경되었는지를 따로 알려주지 않기 때문

 Callback ref 방식을 사용하게 되면 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고, 이를 통해 다른 정보들을 업데이트할 수 있음

- 위 예제 코드에서는 h1 태그의 높이값을 매 번 업데이트 하고 있음

- useCallback() Hook의 의존성 배열로 비여있는 배열(empty array)를 넣었는데, 이렇게하면 h1 태그가 마운트, 언마운트될 때만 callback 함수가 호출되며 재렌더링이 일어날 때는 호출되지 않음