컴포넌트 성능 최적화의 대표적인 방법에 useMemo와 useCallback이 있다.
useMemo 훅에 대해 알아보자
1. useMemo란?
여기서 Memo는 Memoization을 뜻한다. Memoization이란 동일한 값을 리턴하는 함수를 반복적으로 호출해야한다면 맨처음값을 계산할때 해당값을 메모리에 저장해서 필요할때마다 계산하지않고 메모리에서 꺼내쓰는것이다.
자주 필요한값을 캐싱을 해둬서 캐시에서 꺼내 쓴다는 느낌
함수형 컴포넌트는 함수이다.
함수형 컴포넌트를 렌더링 한다 = 함수를 호출한다. => 모든 내부 변수가 초기화 된다.
그렇다고 남용하면 성능이 안좋아질수도 있다.
무거운거만 따로 메모리에 저장해두는거라 별 필요없는거까지 해두면 성능 그닥~~
2. useMemo의 구조
const value = useMemo(() => {
return calculate();
}, [item]);
첫번째 인자로는 콜백함수, 두번째 인자로는 배열을 받는다.
첫번째 인자는 메모이제이션 해줄 값을 계산해서 리턴해주는 함수이다.
두번재 인자 배열은 의존성 배열이라고도 불린다.
useMemo는 배열안의 요소의 값이 업데이트 될때만 콜백함수를 다시 호출해서 메모이제이션 된 값을 업데이트 해서 다시 메모이제이션을 해준다.
만약 빈배열을 넘겨주면 맨처음 컴포넌트가 마운트 되었을떄만 값을 계산하고 이후에는 항상 메모이제이션 된 값을 꺼내 사용하겠죠잉~
예제를 통해서 학습하자
기본코드
import React, { useState } from 'react'
const hardCalculate = (number) => {
console.log('어려운 계산!');
for (let i=0; i<999999999; i++){} //생각하는 시간
return number + 10000;};
function Test(){
const [hardNumber, setHardNumber] = useState(1);
const hardSum = hardCalculate(hardNumber);
return (
<div>
<h3>어려운 계산기</h3>
<input
type="number"
value={hardNumber}
onChange={(e)=> setHardNumber(parseInt(e.target.value))}/>
<span> + 10000 = {hardSum} </span>
</div>
)
}
export default Test
코드 설명:
input에 value에 hardNumber라는게 있다. input에 값을 넣으면 onChange가 동작해서 hardNumber State가 변경된다.
hardSum은 hardCalculate라는 함수가 호출이되면 hardNumber를 넘겨주고 함수의 계산을 완성시키는 것이다.
Test라는 컴포넌트가 호출이 될때마다 렌더링이 된다는것은 Test 라는 함수가 호출이 된다는것이다. 함수가 호출이 되면 함수내부의 변수는 초기화가 된다. 그렇기 떄문에 Test가 반복해서 렌더링이 된다면 내부의 hardSum이라는 변수가 계속 초기화가 된다. 즉, hardCalculate가 반복해서 작동하고 값을 할당 해준다는 것이다. 즉 렌더링이 될때마다 계속 함수가 실행되는 것이다.
실제로 콘솔창을 보면 for반복문 때문에 숫자를 입력할때마다 조금 딜레이가 발생한다.
그렇다면 for반복문이 없는 쉬운계산을 추가한다면 어떻게 될까? 쉬운계산은 먼저 빠르게 계산이 될까?
import React, { useState } from 'react'
const hardCalculate = (number) => {
console.log('어려운 계산!');
for (let i=0; i<999999999; i++){} //생각하는 시간
return number + 10000;};
const easyCalculate = (number) =>{
console.log("쉬워쉬워 계산");
return number + 1;
}
function Test(){
const [hardNumber, setHardNumber] = useState(1);
const [easyNumber, setEasyNumber] = useState(1);
const hardSum = hardCalculate(hardNumber);
const easySum = easyCalculate(easyNumber);
return (
<div>
<h3>어려운 계산기</h3>
<input
type="number"
value={hardNumber}
onChange={(e)=> setHardNumber(parseInt(e.target.value))}/>
<span> + 10000 = {hardSum} </span>
<h3>쉬운 계산기</h3>
<input
type="number"
value={easyNumber}
onChange={(e)=> setEasyNumber(parseInt(e.target.value))}/>
<span> + 1 = {easySum} </span>
</div>
)
}
export default Test
결과는 아니다.
Test 컴포넌트가 실행된다면 내부의 hardSum과 easySum 모두 초기화 되기때문에
어려운 계산과 쉬운계산 모두 다시 실행되야하기때문이다.
이래서 필요한게 useMemo이다.
useMemo는 어떤 조건을 만족해야만 값을 사용하게 해준다.
이제 useMemo를 사용해보자
// const hardSum = hardCalculate(hardNumber);
const hardSum = useMemo(()=>{
return hardCalculate(hardNumber);
}, [hardNumber])
배열에 값을 넣어주면 값이 바뀔때만 안에있는 hardCalculate를 다시 호출해서 hardSum에 할당해준다.
이곳이 바로 그 조건을 넣는곳이다.
즉 hardNumber가 변경 될때만 작동된다.
전에는 쉬운계산기를 작동해도 어려운 계산기도 작동해서 오래걸렸는데, 이젠 쉬운계산기만 작동할때는 어려운계산기는 작동이 안된다.
사실 여기까진 이해를 위해 어떻게 굴러가는건지 구조만 알게된거고, 실제에서는 잘사용되지 않는 구조이다.
진짜 useMemo를 사용하는 예시를 알아보자 ㅎㅎ;; ㅈㅅ..ㅋㅋ!!!
import React from 'react'
import { useState } from 'react'
const Test = () => {
const [number, setNumber] = useState(0);
const [isKorea, setIsKorea] = useState(true);
const location = isKorea ? "한국" : "외국";
return (
<div>
<h2>하루에 몇끼 먹어요?</h2>
<input
type="number"
value={number}
onChange={(e)=> setNumber(e.target.value)}/>
<hr/>
<h2>어느 나라에 있어요?</h2>
<p>나라: {location}</p>
<button onClick={()=> setIsKorea(!isKorea)}>비행기 타자</button>
</div>
)
}
export default Test
간단한 구조이다. 비행기 타자 버튼을 누르면 나라가 한국,외국으로 바뀌는
useEffect(()=>{
console.log("useEffect 호출");
},[location])
location값만 보고있기떄문에 location값이 바뀔때만 useEffect가 실행된다.
만약 location값이 string이 아니라 object라면?
import React from 'react'
import { useEffect } from 'react';
import { useState } from 'react'
const Test = () => {
const [number, setNumber] = useState(0);
const [isKorea, setIsKorea] = useState(true);
const location = {
country: isKorea ? "한국" : "외국",
}
useEffect(()=>{
console.log("useEffect 호출");
},[location])
return (
<div>
<h2>하루에 몇끼 먹어요?</h2>
<input
type="number"
value={number}
onChange={(e)=> setNumber(e.target.value)}/>
<hr/>
<h2>어느 나라에 있어요?</h2>
<p>나라: {location.country}</p>
<button onClick={()=> setIsKorea(!isKorea)}>비행기 타자</button>
</div>
)
}
export default Test
location이 아니라 number만 바꼈는데 왜 useEffect가 작동 됐을까?
이것을 이해하기위해서는 자바스크립트의 원시타입과 객체타입을 알아야한다.
변수는 값을 넣어두는 상자이다. 어떤 변수에 원시타입을 할당하면 상자에 바로들어간다.
객체타입의 값은 크기 때문에 바로 상자에 넣어지지 않고, 메모리를 할당하여 그안에 보관이 되고, 변수안에는 객체가 담긴 메모리 주소가 할당이 된다.
객체 타입에서 두 변수를 비교하면 false가 나온다. 왜냐하면 변수안에는 메모리의 주소가 들어있는데
주소끼리 비교하니까 false가 나온다.
다시 돌아와서
하루에 몇끼 먹어요?를 증가시키면 number state가 바뀌기 때문에 Test라는 함수가 다시 호출이 되는것이다.
그러면
const location = {
country: isKorea ? "한국" : "외국",
}
이것또한 다시 할당이 되고, 다른 메모리 공간에 저장이 된다.
즉, number state가 바뀔떄마다 location의 메모리주소가 계속 바뀌기때문에 useEffect의 의존배열 [location]이 값이 바뀐줄알고 number만 바꿨는데 렌더링 되는것이다.
이것을 해결해 주려면 렌더링이 됐을때 location 변수가 다시 초기화가 되는것을 막아줘야한다.
const location = useMemo(()=>{
return {
country: isKorea ? "한국" : "외국",
};
},[isKorea]);
useMemo를 통해서 isKorea가 바뀔때만 소환한다!!!!
'React' 카테고리의 다른 글
Virtual Dom에 대해(feat.리액트).araboja (0) | 2023.02.04 |
---|---|
useRef는 무엇인가?.araboja (0) | 2022.12.13 |
Redux 와 Redux-Toolkit을 간단하게.araboja (0) | 2022.11.29 |
[React] Axios로 데이터 받아 출력하기 (0) | 2022.10.05 |
레고마켓 만들기 우여곡절 리스트 (0) | 2022.06.29 |