컴포넌트는 기본적으로 상태가 변경되거나 부모 컴포넌트가 렌더링이 될 때마다 리렌더링을 하는 구조이다.
너무 잦은 리렌더링은? → 앱에 좋지 않은 선능을 끼친다!
React Hook은 함수 컴포넌트가 상태를 조작하고 및 최적화 기능을 사용할 수 있게끔 하는 메서드라고 했는데,
그중 렌더링 최적화를 위한 Hook이 있다 → useMemo, useCallback !
useMemo
특정 값(value)을 재사용하고자 할 때 사용하는 Hook
function Calculator({value}){
const result = calculate(value);
return <>
<div>
{result}
</div>
</>;
}
코드 설명 : props로 넘어온 value 값을 calculate라는 함수에 인자로 넘겨서 result 값을 구한 후, <div> 엘리먼트로 출력 중
만약 calculate가 내부적으로 복잡한 연산을 해야하는 함수라 출력되는데 몇 초 이상 걸린다면?
해당 컴포넌트는 렌더링 할 때마다 이 함수를 계속해서 호출할 것이고,
그 때마다 시간이 몇 초 이상 소요가 될 것이다.
→ 렌더링에 영향 → "앱의 로딩 속도가 느리네?" 생각이 듦
/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import { useMemo } from "react";
function Calculator({value}){
const result = useMemo(() => calculate(value), [value]);
return <>
<div>
{result}
</div>
</>;
}
value는 일종의 값으로서, 이 값이 계속 바뀌는 경우라면 어쩔 수 없겠지만,
렌더링을 할 때마다 이 value값이 계속 바뀌는 게 아니라고 가정해보자
↓
그럼 이 값을 어딘가에 저장을 해뒀다가 다시 꺼내서 쓸 수만 있다면 굳이 calculate 함수를 호출할 필요가 없다. → useMemo !
이런 식으로 useMemo를 호출하여 calculate를 감싸주면, 이전에 구축된 렌더링과 새로이 구축되는 렌더링을 비교해
value값이 동일할 경우에는 이전 렌더링의 value값을 그대로 재활용할 수 있게 된다. → Memoization과 긴밀한 관계
Memoizatioin
기존에 수행한 연산의 결과값을 메모리에 저장해두고, 동일한 입력이 들어오면 재활용하는 프로그래밍 기법
→ 알고리즘에 자주 나오는 개념
적절히 사용하면 굳이 중복 연산을 할 필요가 없기 때문에 앱의 성능을 최적화할 수 있다.
useMemo는 바로 이 개념을 이용하여 복잡한 연산의 중복을 피하고 React 앱의 성능을 최적화시킨다.
직접 메모이제이션 개념을 이용하여 로직을 구현할 수도 있으나,
useMemo Hook을 호출한다면 이런 로직을 직접 구현하는 것을 대신해주기 때문데 훨씬 간편하다
useCallback
useMemo와 마찬가지로 메모이제이션 기법을 이용한 Hook
useMemo → 값의 재사용을 위해 사용하는 Hook
useCallback → 함수의 재사용을 위해 사용하는 Hook
function Calculator({x, y}){
const add = () => x + y;
return <>
<div>
{add()}
</div>
</>;
}
코드 설명 : add 함수는 props로 넘어온 x와 y값을 더해 <div> 태그에 값을 출력하고 있다.
→ 이 함수는 해당 컴포넌트가 렌더링 될 때마다 새롭게 만들어질 것이다.
useMemo와 마찬가지로, 해당 컴포넌트가 리렌더링 되더라도 그 함수가 의존하고 있는 값인 x와 y가 바뀌지 않는다고 가정해보자
그렇다면 함수 또한 메모리 어딘가에 저장해 뒀다가 다시 꺼내서 쓸 수 있을 것이다.
이때 useCallback Hook을 사용하면 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환한다.
→ 즉 x와 y값이 동일하다면 다음 렌더링 때 이 함수를 다시 사용 !
/* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import React, { useCallback } from "react";
function Calculator({x, y}){
const add = useCallback(() => x + y, [x, y]);
return <>
<div>
{add()}
</div>
</>;
}
useCallback만 사용해서는 useMemo만큼 최적화를 느낄 수 없다.
useCallback은 함수를 호출하지 않는 Hook이 아니라,
→ 그저 메모리 어딘가에 함수를 꺼내서 호출하는 Hook이기 때문 !
따라서 단순히 컴포넌트 내에서 함수를 반복해서 생성하지 않기 위해서
useCallback을 사용하는 것은 큰 의미가 없거나 오히려 손해인 경우도 있다.
그러면 언제 사용하는 게 좋을까?
→ 자식 컴포넌트의 props로 함수를 전달해 줄 때 !
useCallback과 참조 동등성
useCallback은 참조 동등성에 의존한다.
React는 JavaScript 언어로 만들어진 오픈소스 라이브러리이기 때문에 기본적으로 JavaScript의 문법을 따른다.
JavaScript에서 함수는 객체입니다.
객체는 메모리에 저장할 때 값을 저장하는 게 아니라 값의 주소를 저장하기 때문에,
반환하는 값이 같을지라도 일치연산자로 비교했을 때 false가 출력됩니다.
function doubleFactory(){
return (a) => 2 * a;
}
const double1 = doubleFactory();
const double2 = doubleFactory();
double1(8); // 16
double2(8); // 16
double1 === double2; // false
double1 === double1; // true
double1과 double2는 같은 함수를 할당했음에도 메모리 주소 값이 다르기 때문에 같다고 보지 않는다.
따라서 두 개의 함수는 동일한 코드를 공유하더라도 메모리 주소가 다르기 때문에,
메모리 주소에 의한 참조 비교 시 다른 함수로 본다.
이는 React 또한 같다.
React는 리렌더링 시 함수를 새로이 만들어서 호출을 한다.
새로이 만들어 호출된 함수는 기존의 함수와 같은 함수가 아닙니다.
그러나 useCallback을 이용해 함수 자체를 저장해서 다시 사용하면 함수의
메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다고 볼 수 있다.
따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때
예상치 못한 성능 문제를 막을 수 있다.
'CodeStates > React' 카테고리의 다른 글
Section4 / Unit3 : Custom Hooks (0) | 2023.07.21 |
---|---|
Section4 / Unit3 : Component와 Hook (0) | 2023.07.19 |
리액트 라이브러리: 달력(dayjs) / css(tailwind) (0) | 2023.06.23 |
Section3 / Unit 4 : Redux (0) | 2023.06.22 |
Section3 / Unit4 : 상태 관리, props drilling (0) | 2023.06.20 |