Frontend Study - 2/React

React : useCallback 성능 최적화 어디에 사용하나? + 함수의 재사용, 객체 데이터의 저장과정.

갓데미 2022. 8. 28. 20:03

 

1. 함수, 객체 데이터 저장과정

함수, 객체의 데이터 저장에 대한 개념을 바탕으로 useCallback의 쓰임새에 접근하면 더 이해가 쉽다.

 

자바스크립트에서 함수는 객체 데이터로 인식되며, 

객체 데이터는 원시데이터와 다르게 Heap안에 저장된 메모리 주소 값을 참조하는 식으로 변수에 저장된다. 

 

 

ex) let melon = { name: melon, color : green } ;

 

1. Heap 에 오브젝트 데이터 값 저장. -> { name: melon, color : green } 

2. Heap에 저장된 곳의 주소값 -> Ox1212

3. 그 주소값을 call stack 메모리셀에 전달 & 저장.

4. 주소값이 저장되어 있는 데이터값의 주소 확인. -> Ox0013

5. 해당 주소를 변수 melon 에 저장.

 

 

 

여기서 유의해야 할 점은, 원시데이터는 두 변수 안의 내용이 같을 경우, 동일한 주소값을 참조하기 때문에 두 변수를 같다고 보았지만,

객체는 Memory Heap 내부에서 다른 주소값을 가지고 있기 때문에 다른 값으로 본다는 것이다.

 

 

 

 

2. useCallback

 

아래의 경우 Price라는 컴포넌트가 재렌더링이 되면, add라는 함수도 같이 재렌더링 된다.

굳이 부모의 재렌더링에 반응하여 함수가 무조건적으로 재렌더링 될 필요가 없을 때,

useCallback을 이용하면 해당 함수의 재렌더링 조건을 지정할 수 있다. 

 

또한 이 때 동일한 add 함수가 렌더링만 다시 되는 것처럼 보이지만 사실은 새로운 add 함수가 계속 생겨나면서 재렌더링이 일어나게 된다. add 함수 안의 값은 같지만 결국 다른 주소값을 참조하는 함수가 생성되는 것이기 때문이다. 새로운 함수의 생성에 리소스가 많이 소요되는 것은 아니다. 하지만 새로 생성하지 않고, 기존 함수를 재사용 했을 때 유용하게 사용함으로써 성능을 최적화 시킬 수 있다.

 

const Price = () => {
	const add = () => x + y 
	return (
    	<div> hi </div>
    )
}

 

useCallback hook을 사용하면 부모 컴포넌트가 재렌더링 되어도 함수가 자동으로 재렌더링 되지 않는다. 

 

 

const Price = () => {
	const add = useCallback(() => x + y, [x, y]);
	return (
    	<div> hi </div>
    )
}

 

위와 같이 적용해 본다면, Price 컴포넌트가 재렌더링 되어도 add함수는 재렌더링, 즉 새롭게 생성 되지 않는다. 

add함수 안에 배열인자로 넣어준 x,y값에 변화가 있을 때만 재렌더링이 진행된다.

useEffect처럼 ' ,[] ' 를 추가하여 dependency 값을 넣어 줄 수 도 있다. 

 

 

+ 추가예시

import React, { useState, useEffect } from "react";


const UserData = ({userId}) => {
	const [profile, setProfile] = useState();
    
	const fetchUser = () =>
    	fetch('https://api.com/${userId}')
        .then((response) => response.json())
        .then(({user}) => user);
    
    useEffect(() => {
    fetchUser().then(user => setProfile(user));
    , [fetchUser]}
 ...
 }

 

위 소스에서 useEffect 부분을 보면, dependency안에 fetchUser 함수를 넣어 주었다.

fetchUser 함수에 변화가 있을 때마다 setProfile을 통해서 profile이라는 state에 user값을 넣게 된다.

이 때 state값이 변경되는 것이기 때문에 state가 포함된 DOM에는 다시 재렌더링이 일어나게 된다.

 

UserData 컴포넌트가 재렌더링 되고 그 안의 fetchUser라는 함수에도 자연스레 재렌더링이 일어난다.

이 때 위에서 이해한 개념을 통해서 fetchUser라는 함수가 재렌더링 될 때 기존 fetchUser함수의 재사용이 아닌, 새로운 fetchUser라는 함수로 다시 새롭게 생성된다는 것을 알 수 있다. 이것은 곧 fetchUser함수가 변화한다는 의미이고 그 변화는 다시 useEffect를 실행하게 하고 무한 렌더링이 일어나게 된다. 

 

import React, { useState, useEffect } from "react";


const UserData = ({userId}) => {
	const [profile, setProfile] = useState();
    
	const fetchUser = useCallback(() =>
    	fetch('https://api.com/${userId}')
        .then((response) => response.json())
        .then(({user}) => user),
        [userId] );
    
    useEffect(() => {
    fetchUser().then(user => setProfile(user));
    , [fetchUser]}
 ...
 }

 

위와 같이 useCallback 으로 묶어주게 되면 문제가 해결된다. 이 경우 컴포넌트가 재렌더링 된다고 해서 함수가 재렌더링 되는 것이 아니라, userId값이 변화 할때만 fetchUser 함수가 재 렌더링 되기 때문이다. 

 

+ useMemo는 함수의 연산량이 많을때 이전 결과값을 재사용하는 목적이고,

useCallback은 함수가 재생성 되는것을 방지하기 위한 목적이다.

 

+ React.memo는 이전 props와 현재 props 간에 얕은(shallow) 비교를 한다. 때문에 props가 참조타입일 경우, 변수의 내용은 변하지 않았어도 재정의로 인해 주소값은 다를 수 있다. 따라서 의도치않은 리렌더링이 발생할 수 있다.

+ 객체 뿐만 아니라 함수도 참조타입의 변수이며, 컴포넌트의 props로 전달될 수 있기 때문에 불필요하게 재정의되는 것을 막아줘야한다. useCallback은 첫 번째 인자로 들어오는 함수가 재정의되는 것을 막아주는 hook이다.

 

참고사이트

https://www.daleseo.com/react-hooks-use-callback/