React 공부 (10) 훅

seon·2024년 2월 15일

Web

목록 보기
17/33
post-thumbnail

# useMemo()

useMemo() : Memoized value 를 리턴하는 Hook

Memoization

for 최적화.
비용이 높은, 연산양이 많은 함수 호출 결과를 저장해 두었다가
같은 입력 값으로 함수를 호출하면 새로 함수를 호출하지 않고
이전에 저장해 놨던 호출 결과를 바로 반환하는 것

함수 호출 결과를 받기까지의 시간도 짧아질 뿐더러, 불필요한 중복 연산x
=> 컴퓨터의 자원을 적게 쓰게됨

  • Memoization이 된 결과값 = Memoized value
    ('Memo' = 메모하다, 메모를 해 두었다가 나중에 다시 사용하는 것)

  • useMemo() 훅은 파라미터로 Memoized value를 생성하는 create 함수와 의존성 배열을 받습니다.

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

  • 빠른 렌더링 속도

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

그렇기 때문에 일반적으로 렌더링이 일어나는 동안 실행되서는 안될 작업을 useMemo() 함수에 넣으면 안됩니다.

예를 들면 useEffect()에서 실행되어야 할 Side effect 같은 것이 있습니다. 서버에서 데이터를 받아 오거나 수동으로 DOM을 변경하는 작업 등은 렌더링이 일어나는 동안 실행되서는 안되기 때문에 useMemo() 훅 함수에 넣으면 안되고, useEffect() 훅을 사용해야 합니다.

  • 의존성 배열을 넣지 않고 useMemo() 훅을 사용하는 것은 아무런 의미가 없다.
  • 결국 마운트 이후에는 값이 변경되지 않는 것이죠
  • 따라서 마운트 시점에만 한번 값을 변경할 필요가 있을 경우에는 이렇게 사용하면 됩니다.

하지만 대부분의 경우에는 useMemo()의 의존성 배열에 변수들을 넣고 해당 변수들의 값이 바뀜에 따라 새로 값을 계산해야 할 경우에 사용합니다.


# useCallback()

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

  • useMemo() 훅과 마찬가지로 함수와 의존성 배열을 파라미터로 받습니다.
  • 파라미터로 받는 이 함수를 callback이라고 부릅니다.
  • 의존성 배열에 있는 변수 중 하나라도 변경되면 memoization된 callback 함수를 반환합니다.
  • 의존성 배열에 따라 memoized 값을 반환한다는 점에서 useMemo()훅과 완전히 동일합니다.

만약 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>
  );
}
  • 특정 변수의 값이 변한 경우에만 함수를 다시 정의하게 되므로
  • 함수가 다시 정의되지 않는 경우에는 자식 컴포넌트도 재렌더링이 일어나지 않습니다.
  • 위 경우는 의존성 배열에 빈 배열이 들어갔기 때문에
    컴포넌트가 처음 mount되는 시점에만 함수가 정의되고 이후에는 다시 정의되지 않으며 결국 컴포넌트도 불필요하게 재렌더링이 일어나지 않게 됩니다.

# useRef()

useRef() : Reference를 사용하기 위한 Hook

그렇다면 Reference란 무엇일까요?

Reference

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

그리고 useRef() 훅은 바로 이 reference 객체를 반환합니다.


refObject.current

  • reference 객체에는 current 라는 속성이 있는데, 이것은
  • 현재 레퍼런스하고 있는 엘리먼트 를 의미한다고 보시면 됩니다.

  • 파라미터로 초깃값 을 넣으면 해당 초깃값으로 초기화된 reference 객체를 반환합니다.
  • 만약 초깃값=null이라면 current 값이 null인 reference 객체가 반환되겠죠?
  • 이렇게 반환된 reference 객체는 컴포넌트의 lifetime 전체에 걸쳐서 유지됩니다.
    즉 컴포넌트가 마운트 해제 전까지는 계속 유지된다는 것이죠

쉽게 말해 useRef 훅은 변경 가능한 current 라는 속성을 가진 하나의 상자라고 생각하면 됩니다.

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>
	</>
  );
}
  • 이 코드는 useRef() 훅을 사용하여 버튼 클릭 시 input에 Focus를 하도록 하는 코드입니다.
  • 초기값으로 null 을 넣었고
  • 결과로 반환될 inputElem이라는 레퍼런스 객체를 input 태그에 넣어줬습니다. <input ref={inputElem} .../>
  • 그리고 버튼 클릭시 <button onClick={onButtonClick}.../> 호출되는 함수( onButtonClick )에서, inputElem.current 를 통해 실제 엘리먼트에 접근하여 focus() 함수를 호출하고 있습니다.

이전에 웹사이트 개발을 해 본 분들은 레퍼런스와 관련하여 DOM에 접근하기 위해 사용하는 ref 속성에 익숙할 수도 있습니다.

<div ref={myRef} />

비슷하게 리액트에서는 위와 같이 코드를 작성하면 노드가 변경될 때마다
myRef의 current 속성에 현재 해당되는 DOM 노드를 저장합니다.
ref 속성과 기능은 비슷하지만 useRef 훅은 클래스의 인스턴스 필드를 사용하는 것과 유사하게 다양한 변수를 저장할 수 있다는 장점이 있습니다.
이런 것이 가능한 이유는 useRef 훅은 일반적인 자바스크립트 객체를 리턴하기 때문입니다.

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

라고 생각할 수도 있습니다. 물론 그렇게 해도 목적 달성은 가능할 수 있지만 요즘에 훅을 사용하는 것과 직접 current 속성이 포함된 모양의 객체를 만들어 사용하는 것의 차이점은, useRef 훅은 매번 렌더링될 때마다 항상 같은 레퍼런스 객체를 반환한다는 것입니다.


그리고 중요하게 기억해야 할 점은,

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

는 것입니다.
그래서 current 속성을 변경한다고 해서 재렌더링이 일어나지는 않습니다.

따라서 ref에 DOM 노드가 연결되거나 분리되었을 경우에 어떤 코드를 실행하고 싶다면 CallBack ref를 사용해야 합니다.

Callback ref

  • DOM 노드의 변화를 알기 위한 가장 기초적인 방법으로 callback ref 를 사용하는 방법이 있습니다.
  • 리액트는 다른 노드에 연결될 때마다 callback을 호출하게 됩니다.
  • 이 예제 코드를 한번 보겠습니다.
function MeasureExample(props) {
  const [height, setHeight] = useState(0);
  
  const measureRef = useCallback(node => {
    if (node != null) {
      setHeight(node.getBountingClientRect().height);
    }
  }, []);
 
  return (
    <>
    	<h1 ref={measureRef}>안녕, 리액트<h1>
    	<h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
    </>
  );
}
  • 이 코드에는 reference를 위해서 useRef() 훅을 사용하지 않고 useCallback() 훅을 사용하는 callBack ref 방식을 사용했습니다.
  • useRef() 훅을 사용하게 되면 레퍼런스 객체가 current 속성이 변경되었는지를 따로 알려주지 않기 때문이죠.
  • Callback ref 방식을 사용하게 되면, 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고 이를 통해 다른 정보들을 업데이트할 수 있습니다.
  • 예제 코드에서는 h1 태그의 높이 값을 매번 업데이트하고 있습니다.
  • 그리고 useCallback 훅의 의존성 배열로 비어있는 배열, 즉 empty array를 넣었는데 이렇게 하면 h1 태그가 마운트 언마운트될 때만 콜백 함수가 호출되면, 재렌더링이 일어날 때에는 호출되지 않습니다.

📍정리

  • useMemo() : Memoized value 를 리턴하는 Hook
useMemo(() => 함수, 의존성 배열);

-전달되는 함수는 렌더링하는 동안 실행됨

  • useCallback() : useMemo() Hook 과 유사하지만 값이 아닌 함수를 반환
useCallback(함수, 의존성 배열);

-의존성 배열에 있는 변수 중 하나라도 변경될 때만 memoization된 callback 함수를 재정의 후 반환

  • useRef() : Reference를 사용하기 위한 Hook
useRef(초깃값);

- 해당 초깃값으로 초기화된 reference 객체( 특정 컴포넌트에 접근할 수 있는 객체 )를 반환
- refObject.current : current 는 reference 객체의 속성. 현재 참조하고 있는 엘리먼트를 말함
=> useRef 훅은, "변경 가능한 current 라는 속성을 가진 하나의 상자"이다.
- current 속성을 변경한다고 해서 재렌더링이 일어나지는 않는다.
=> Callback ref 방식 : useCallback()을 이용해서 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있음 ( 빈 의존성 배열[]을 넣으면 마운트/언마운트될 때만. )



profile
🌻

0개의 댓글