useMemo()
useState, useEffect 외에 자주 사용하는 useMemo를 학습해보자.
useMemo 훅은 Memoized value를 return 하는 훅이다.
useMomo 훅과 바로 뒤에 나올 useCallback훅을 이해하기 위해서는 먼저 Memoized value가 무엇인지 알고있어야 한다.
useMemo와 useCallback 훅에서는 memoization이라는 개념이 나오는데, 컴퓨터 분야에서 memoization은 최적화를 위해서 사용하는 개념이다.
비용이 높은, 즉 연산량이 많이 드는 함수의 호출 결과를 저장해 두었다가 같은 입력 값으로 함수를 호출하면 새로 함수를 호출하지 않고 이전에 저장해 놨던 호출 결과를 바로 반환하는 것이다.
이렇게 하면 결과적으로 함수 호출 결과를 받기까지 걸리는 시간도 짧아질 뿐더러 불필요한 중복 연산도 하지 않기 때문에 컴퓨터 자원을 적게 쓰게 된다.
메모이제이션이 된 결과 값을 영어로는 Memoized Value라고 부른다.
메모이제이션의 메모는 우리가 흔히 메모하다 라고 표현하는 것과 비슷한 맥락이라고 생각하면 쉽게 이해할 수 있다.
const memoizedValue = useMemo(
() => {
// 연산량이 높은 작업을 수행하여 결과를 반환
return computeExpensiveValue(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
)
useMome 훅은 파라미터로 memoizedValue를 생성하는 create함수와 의존성 배열을 받는다.
메모이제이션의 개념처럼 의존성 배열에 들어있는 변수가 변했을 경우에만 새로 create함수를 노출하여 결과값을 반환하며 그렇지 않은 경우에는 기존 함수의 결과 값을 그대로 반환한다.
useMemo 훅을 사용하면 컴포넌트가 다시 렌더링 될 때마다 연산량이 높은 작업을 반복하는 것을 피할 수 있다.
useMemo 훅을 사용할 때 기억해야 할 점은 useMemo로 전달된 함수는 렌더링이 일어나는 동안 실행된다는 점인데, 그렇기 때문에 일반적으로 렌더링이 일어나는 동안 실행돼서는 안될 작업을 useMemo의 함수에 넣으면 안된다.
예를 들면 useEffect 훅에서 실행되어야 할 사이드 이펙트 같은 것이 있는데, 서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 작업 등은 렌더링이 일어나는 동안 실행되어서는 안되기 때문에 useMemo 훅 함수에 넣으면 안되고, useEffect를 사용해야 한다.
const momoizedValue = useMemo(
() => computeExpensiveValue(a, b)
)
그리고 또한 위 코드와 같이 의존성 배열을 넣지 않을 경우 렌더링이 일어날 때마다 매번 실행된다.
따라서 useMemo 훅에 의존성 배열을 넣지 않고 사용하는 것은 아무런 의미가 없다.
const momoizedValue = useMemo(
() => {
return computeExpensiveValue(a, b);
},
[]
)
그리고 이렇게 의존성 배열에 빈 배열을 넣게 되면 컴포넌트가 마운트 될 때만 create 함수가 호출 되는데, 결국 마운트 이후에는 값이 변경되지 않는다는 소리다.
따라서 마운트 시점에만 한번 값을 계산할 필요가 있을 경우에는 위에 코드처럼 두번째 파라미터에 빈 배열을 사용하면 된다.
하지만 대부분의 경우에는 useMemo 훅에 의존성 배열의 변수들을 넣고 해당 변수들의 값이 바뀜에 따라 새로 값을 계산해야 할 경우에 사용한다.
useCallback()
useCallback 훅은 이전에 나온 useMemo 훅과 유사한 역할을 하는데, 한가지 차이점은 값이 아닌 함수를 반환한다는 점이다.
쉽게 말하면 컴포넌트가 렌더링 될 때마다 매번 함수를 새로 정의하는 것이 아니라 의존성 배열의 값이 바뀐 경우에만 함수를 새로 정의해서 return 해준다는 소리다.
const memoizedCallback = useCallback(
() => {
doSomething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
useCallback 훅은 useMemoHook과 마찬가지로 함수와 의존성 배열을 파라미터로 받는다.
useCallback 훅에서는 파라미터로 받는 이 함수를 callback이라고 부르며, 의존성 배열에 있는 변수 중 하나라도 변경되면 메모이제이션된 콜백 함수를 반환한다.
의존성 배열에 따라 메모이즈드 값을 반환한다는 점에서는 유즈 메모 훅과 완전히 동일하다.
useCallback(함수, 의존성 배열);
useMemo(() => 함수, 의존성 배열);
위 코드 두개는 동일한 역할을 한다고 볼 수 있다.
만약 useCallback 훅을 사용하지 않고 컴포넌트 내에 함수를 정의한다면 매번 렌더링이 일어날때마다 함수가 새로 정의되기 때문에 useCallback을 사용하여 특정 변수의 값이 변한 경우에만 함수를 다시 정의하도록 해서 반복 작업을 없애주는 것이다.
import React, { useState } from "react";
function ParentComponent(props) {
const [count, setCount] = useState(0);
// 재렌더링 될 때마다 매번 함수가 새로 정의됨
const handleClick = (e) => {
// 클릭 이벤트 처리
};
return (
<div>
<p>총 {count}번 클릭했습니다. </p>
<button onClick={() => setCount(count + 1)}> 클릭</button>
<CildeComponent handleClick={handleClick}> </CildeComponent>
</div>
);
}
export default ParentComponent;
예를 들어 이 코드처럼 useCallback을 사용하지 않고 컴포넌트 내에서 정의한 함수를 자식 컴포넌트의 props로 넘겨 사용하는 경우에 부모 컴포넌트가 다시 렌더링이 될 매번 자식 컴포넌트도 다시 렌더링 된다.
import React, { useCallback, useState } from "react";
const ChildComponent = "<div></div>";
function ParentComponent(props) {
const [count, setCount] = useState(0);
// 재렌더링 될 때마다 매번 함수가 새로 정의됨
// const handleClick = (e) => {
// 클릭 이벤트 처리
// };
const handleClick = useCallback((e) => {
// 클릭이벤트 처리
}, []);
return (
<div>
<p>총 {count}번 클릭했습니다. </p>
<button onClick={() => setCount(count + 1)}> 클릭</button>
<ChildComponent handleClick={handleClick}></ChildComponent>
</div>
);
}
export default ParentComponent;
하지만 useCallback을 사용하면 특정 변수의 값이 변한 경우에만 함수를 다시 정의하게 되므로 함수가 다시 정의되지 않는 경우에는 자식 컴포넌트도 재렌더링이 일어나지 않는다.
이 경우에는 의존성 배열에 빈 배열이 들어갔기 때문에 컴포넌트가 처음 마운트 되는 시점에만 함수가 정의되고 이후에는 다시 정의되지 않으며 결국 자식컴포넌트도 불필요하게 재렌더링이 일어나지 않게 된다.
useRef()
useRef 훅은 레퍼런스를 사용하기 위한 훅이다. 그렇다면 레퍼런스란 무엇일까?
리액트에서 레퍼런스란 특정 컴포넌트에 접근할 수 있는 객체를 의미한다.
그리고 useRef 훅은 바로 이 레퍼런스 객체를 반환한다.
레퍼런스 객체에는 current라는 속성이 있는데 이것은 현재 레퍼런스하고 있는 element를 의미한다고 보면 된다.
const refContainer = useRef(초기값);
useRef 훅은 위와 같이 사용하는데, 파라미터로 초기값을 넣으면 해당 초기값으로 초기화된 레퍼런스 객체를 반환한다.
만약 초기값이 null이라면 current의 값이 null인 레퍼런스 객체가 반환된다.
이렇게 반환된 레퍼런스 객체는 컴포넌트의 라이프타임 전체에 걸쳐서 유지되는데, 즉 컴포넌트가 마운트 해제전까지는 계속 유지된다는 말이다.
쉽게 말해 useRef 훅은 변경 가능한 current라는 속성을 가진 하나의 상자라고 생각하면 될 거 같다.
import React, { useRef } from "react";
export const TextInputWithFocusButton = (props) => {
const inputElem = useRef(null);
const onButtonClick = () => {
// current는 마운트된 iput element를 가리킴
inputElem.current.focus();
};
return (
<>
<input type="text" ref={inputElem} />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
};
이 코드는 useRef를 사용하여 버튼 클릭 시 input에 포커스를 하도록 하는 코드다.
초기값으로 null을 넣었고 결과로 반환된 inputElem이라는 레퍼런스 객체를 input 태그에 넣어줬다.
그리고 버튼 클릭 시 호출되는 함수에서 inputElim.current를 통해 실제 element에 접근하여 포커스 함수를 호출하고 있다.
웹개발을 하면서 레퍼런스와 관련하여 DOM에 접근하기 위해 사용하는 속성을 사용했던 적이 있다.
<div ref={myRef} />
비슷하게 리액트에서는 위와 같이 코드를 작성하면 노드가 변경될 때마다 myRef의 current속성에 현재 해당되는 DOM 노드를 저장한다.
ref 속성과 기능은 비슷하지만 useRef 훅은 클래스의 instance 필드를 사용하는 것과 유사하게 다양한 변수를 저장할 수 있다는 장점이 있다.
이런 것이 가능한 이유는 useRef 훅이 일반적인 자바스크립트 객체를 리턴하기 때문이다.
그럼 내가 직접 current 속성이 포함된 자바스크립트 객체를 만들어 써도 되는건 아닌가? 라는 의문을 가지게 되는데, 물론 그렇게 해도 목적 달성은 가능할 수 있지만 useRef 훅을 사용하는 것과 직접 current 속성이 포함된 모양의 객체를 만들어 사용하는것의 차이점은 useRef 훅은 매번 렌더링 될 때마다 항상 같은 레퍼런스 객체를 반환한다는 것이다.
그리고 한가지 기억해야 할 점은 useRef 훅은 내부의 데이터가 변경되었을 때 별도로 않는다는 점이다.
그래서 current 속성을 변경한다고 해서 재렌더링이 일어나진 않는다.
따라서 ref에 DOM 노드가 연결되거나 분리되었을 경우에 어던 코드를 실행하고 싶다면 callback ref를 사용해야한다.
DOM노드의 변화를 알기 위한 가장 기초적인 방법으로 callback ref를 사용하는 법이 있는데, 리액트는 ref가 다른 노드에 연결될 때마다 callback을 호출하게 된다.
import { useCallback, useState } from "react";
export default function MeasureExample(props) {
const [height, setHeight] = useState(0);
const measuredRef = useCallback((node) => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>안녕, 리액트</h1>
<h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
</>
);
}
위 예제코드를 한번 확인해보자.
이 코드에는 레퍼런스를 위해서 useRefHook을 사용하지 않고 useCallback 훅을 사용하는 callbackRef 방식을 사용했다.
useRef 훅을 사용하게 되면 레퍼런스 객체가 current 속성이 변경되었는지를 따로 알려주지 않기 때문이다.
하지만 callbackRef 방식을 사용하게 되면 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고 이를 통해 다른 정보들을 업데이트할 수 있다.
이 예제코드에서는 h1 태그의 높이 값을 매번 업데이트하고 있으며, useCallback 훅 의존성 배열로 비어있는 배열, 즉 empty array를 넣었는데 이렇게 하면 h1태그가 mount, unmount 될 때만 callback 함수가 호출되며 재렌더링이 일어날 때에는 호출되지 않는다.
'밥줄 > React' 카테고리의 다른 글
[React] React 학습 (7) - Hooks (useState, useEffect) (2) | 2024.11.04 |
---|---|
[React] React 에러 해결 - Component Key (2) | 2024.11.04 |
[React] React 학습 (6) - State & Lifecycle (2) | 2024.11.04 |
[React] React 학습 (5) - Component 예제 (2) | 2024.11.01 |
[React] React 학습 (4) - Props (4) | 2024.10.31 |