[React] React Hook의 종류 - useMemo(), useCallback(), useRef()

    Hook의 종류 - 2


    useMemo()

    Memoized value를 리턴하는 Hook

     

    Memoization

    비용이 높은 함수의 호출 결과를 저장해 뒀다가, 같은 입력값으로 함수를 호출하면 저장해둔 호출 결과를 바로 반환한다.

    ➰ Memo를 해두었다가 나중에 다시 사용하는 것!

    • 속도를 향상하고, 불필요한 중복 연산을 제거할 수 있다.
    • Memoized Value : Memoization된 결과값

     

    사용법

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

    2개의 파라미터를 갖는다.

    • memoization 해줄 값을 계산해서 리턴해주는 함수 (callback)
      ⏩ useMemo가 리턴하는 값, memoizedValue
    • 의존성 배열

     

    의존성 배열에 있는 변수가 업데이트 됐을 경우에만,

    1. callback을 호출하여
    2. memoization된 값을 업데이트(re-memoization),
    3. 결괏값을 반환하고

    그렇지 않을 경우 기존 함수의 결과값을 그대로 반환한다.

     

     

    useMemo()로 전달된 함수는 렌더링이 일어나는 동안 실행된다.

    ⏩ 렌더링이 일어나는 동안 실행되어선 안 되는 작업을 useMemo()에 넣어서는 안 된다.

    • 예를 들면, 'Side Effect'   ✅ useEffect()에서 실행되어야 한다.
      ➰ 서버에서 데이터를 받아오거나 수동으로 돔을 변경하는 작업 등

     

    📌 예제1-1. useMemo() - 의존성 배열 생략

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

    의존성 배열을 넣지 않으면, 매 렌더링마다 함수가 실행
    useMemo()에서 배열을 넣지 않는 것은 아무 의미 없다.

     

     

    📌 예제1-2. useMemo() - 의존성 배열에 빈 배열 할당

    const memoizedValue = useMemo(
      () => {
        return computeExpensiveValue(a,b)
      }, []
    );
    • 의존성 배열이 빈 배열일 경우, 컴포넌트가 마운트 될 때만 callback이 호출
      ⏩ 마운트 이후에는 값이 변경되지 않음
    • 마운트 시점에만 값을 계산할 필요가 있을 경우에 이렇게 사용
    • 하지만 대부분의 경우, 의존성 배열에 값이들어 간다.

     

    useCallback()

    useMemo()와 유사하지만, 값이 아닌 함수를 반환한다.

     

    사용법

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

     

    2개의 파라미터를 갖는다.

    1. callback
    2. 의존성 배열

    컴포넌트가 렌더링 될 때마다 함수를 새로 정의하는 것이 아니라, 의존성 배열이 바뀐 경우에만 함수를 새로 정의한다.

    의존성 배열에 있는 변수 중 하나라도 변경되면 memoization 된 callback 반환한다.

     

     

    Summary

    아래 두 코드는 동일한 역할을 한다.

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

     

     

    코드로 이해하기

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

    useCallback()을 사용하여 특정 변수의 값이 변한 경우에만 함수를 다시 정의하도록 한다.

     

     

    📌 예제2-1. useCallback() 사용하지 않은 경우

    import React, { 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>
      )
    }

     

     

    📌 예제2-1. useCallback() 사용한 경우

    import React, { useState, useCallback } 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>
      )
    }
    • 특정 변수의 값이 변한 경우에만 함수를 다시 정의
      ⏩ 함수가 다시 정의되지 않는 경우에는, 자식 컴포넌트(ChildComponent)도 재렌더링이 일어나지 않는다.
    • 의존성 배열이 빈 배열이므로, 컴포넌트가 처음 마운트 되는 시점에만 함수가 정의된다.

     

    useRef()

    Reference를 사용하기 위한 Hook으로, Reference 객체를 반환한다.

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

     

    refObject.current

    • current : Property of refObject
    • 현재 참조하고 있는 Element를 나타낸다.

     

    사용법

    const refContainer = useRef(초깃값)

    .current의 값이 (초깃값)인 ref 객체를 반환한다.

     

    반환된 ref 객체는 current의 Lifecycle 전체에 걸쳐서 유지된다.

    ⏩ 즉, 컴포넌트가 마운트 해제되기 전까지는 계속 유지된다.

     

    본질적으로 useRef는 .current  프로퍼티에 변경 가능한 값을 담고 있는 "상자"와 같습니다.
    - 리액트 공식문서

     

     📌예제3. useRef()

    function TextInputWithFocusButton(props) {
      const inputElem = useRef(null);
    
      const onButtonClick = () => {
        // current는 마운트 된 input element를 가리킴
        inputElem.current.focus();
      }
    
      return (
        <>
          <input ref={inputElem} type="text"/>
          <button onClick={onButtonClick}>
            Focus the input
          </button>
        </>
      )
    }
    • 버튼 클릭 시 input에 포커스
    • 작동 원리
      1. useRef() 를 사용하여 Ref 객체를 만들고,
      2. 이 객체를 우리가 선택하고 싶은 DOM 에 ref 값으로 설정해준다.
      3. 그러면, Ref 객체의 .current 값은 우리가 원하는 DOM 을 가르키게 된다.

     

    useRef()는 내부의 데이터가 변경되어도 따로 알리지 않는다.

    ⏩ 즉, current 속성을 바꾼다고 해서 다시 렌더링이 일어나진 않는다.

    ✅ Ref에 DOM 노드가 연결/분리 되었을 경우, 어떤 코드를 실행하고 싶다면 Callback ref 사용한다.

     

    Callback ref

    ref가 다른 노드에 연결될 때 마다 callback을 호출한다.

    ✅ ref 에 useRef로 반환된 값을 넘기는게 아니라 함수를 넘긴다.

     

    📌 예제4. Callback ref

    function MeasureExample(props) {
      const [ height, setHeight ] = useState(0);
    
      const measuredRef = useCallback(node => {
        if(node!== null){
          setHeight(node.getBoundinClientRect().height);
        }
      }, []);
    
      return (
        <>
          <h1 ref={measuredRef}>안녕, 리액트</h1>
          <h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
        </>
      )
    }
    • Ref를 위해서 useRef()를 사용하지 않고, useCallback() 사용✅ ref attribute에 DOM이 아닌 함수를 할당한다.
    • 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있다.
    • 의존성 배열에 빈 배열 할당 ⏩ h1 태그가 mount/unmount할 때만 callback 호출


    🚀 참고

     

     더 공부할 것 : 영상보고 정리

    댓글