React/React Hooks

[React]컴포넌트 성능 최적화(useMemo , useCallback, React.Memo)

이지영 2022. 7. 15. 14:06

컴포넌트라는 함수가 렌더링이 될 때마다 변수가 다시 초기화가 되게 되므로 "새로 만들어진 함수 객체의 주소 값을 다시 할당받게 되어진다". 이러한 불필요한 초기화 또는 렌더링을 막아주기 위해 아래와 같은 React hooks를 이용하여 더 성능을 최적화 시켜줄 수 있다.

useMemo

만약 컴포넌트 내에 어떤 함수가 값을 리턴하는데 많은 시간을 소요한다면, 이 컴포넌트가 리렌더링 될 때마다 함수가 호출되면서 많은 시간을 소요하게 될 것이고, 그 함수가 반환하는 값을 하위 컴포넌트가 사용한다면 그 하위 컴포넌트는 매 함수호출마다 새로운 값을 받아 리렌더링할 것이다.

 

이와 같은 문제는 useMemo를 통해 함수가 하위 컴포넌트를 최적화 함으로써 해결할 수 있다. useMemo를 사용해서 momoization 해주면 이러한 상황을 간편히 해결 해줄수 useMemo는 처음에 계산한 값을 메모리에 저장하기 때문에 컴포넌트가 반복적으로 렌더링 돼도 함수를 다시 호출 하지 않고 이전에 계산된 결과 값을 메모리에 꺼내와서 재사용 할 수 있게 해준다.

 

  • "Memoization": 동일한 값을 리턴하는 함수를 반복적으로 호출해야한다면, 이미 계산해 놓은 값을 메모리상에 저장해두고 필요할때마다 꺼내서 재사용 하는 기법이다

❕이러한 성능을 가진 useMemo도 무분별하게 남용하면 성능에 무리가 갈 수 있다!
useMemo를 사용한다는 것은 값을 재활용하기 위해 따로 "메모리를 소비해서 저장을 해놓는다"는 것이기 때문에 불필요한 값까지 모두 메모이제이션을 해버리면 오히려 성능이 악화될 수 있다.❕

useMemo 사용방법

function Component() {
	const value = 여려운함수();

	return <div>{value}</div>
}

function 어려운함수() {
	return 10;
}
function Component() {
	const value = useMemo(() => 어려운연산함수(), []);
    
    return <div>{value}</div>
}

=>첫번째 인자로 callback 함수를 받고 return 값으로 memoization 할 값을 할당해 주면된다

    두번째 인자로 배열을 받는데 이배열은 의존성 배열로써 그값이 바뀔때만 콜백함수를 다시 호출하여 할당 해 주는것

const value = useMemo(() => {
	return calculate();
}. [hard]);

즉  useMemo는 처음에 계산된 결과 값을 메모리에 저장해서 컴포넌트가 반복적으로 렌더링이 되어도 계속 어려운함수()를 다시 호출하지 않고 이전에 이미 계산되었던 값을 메모리에서 꺼내와서 "재사용"할 수 있게 해준다.

 

UseCallback

useCallback 은 useMemo 와 비슷한 Hook 입니다.

useMemo 는 특정 결과값을 재사용 할 때 사용하는 반면, useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용합니다.

 const someFunction = useCallback(() => { 
    	console.log(`someFunc: number: ${number}`);
		return;
    }, [number]); //!
    
        useEffect(() => {
		console.log("someFunction이 변경되었습니다.");
	}, [someFunction]);

리액트 훅 중 하나인 useEffect의 디펜던시에는함수를 담아주게 되면 해당 함수가 변경되어야지만 초기화가 될 것 같지만  렌더링이 될 때마다 변수에 담아준 함수는 다른 주소 값을 변수에 계속 할당하기 때문에 디펜던시의 담은 함수가 변경될 때 외에도 해당 컴포넌트가 렌더링이 될 때마다 useEffect내부의 코드가 계속 동작을 하게 되었던 것.

 

number state가 바껴서 App 컴포넌트가 렌더링이 된다면 someFunction 의 함수 객체가 다시 새로 생성이 돼서 또 다른 메모리 공간 안에 저장이 된다. 그렇게 되면 someFunction 이라는 메모리 안에는 이전과는 다른 주소 값이 할당되어지게 되는 것이다. 그렇기 때문에 useEffect의 입장에서는 이전 렌더링과 다음 렌더링 때 디펜던시 안에 someFunction 의 주소 값을 비교하면  사실은 동일한 값을 가진 객체임에도 두 개의 값이 "다르다"라고 인식하게 되는 것이다.

 

 useCallback을 사용해서 App 컴포넌트가 렌더링이 되더라도 someFunction이 바뀌지 않도록

위와 같이 코드를 변경해주면 number 값이 바뀌게 되더라도 더 이상 useEffect가 리렌더링 되지 않는 것을 확인할 수 있다.

 

React.memo

React.memo는 Higher-Order Components(HOC)이다.

Higher-Order Components(HOC)고차 컴포넌트란 컴포넌트를 인자로 받아 새로운 컴포넌트롤 다시 return해주는 함수이다.

 

리액트에서 부모 컴포넌트가 렌더링 될 때 해당 컴포넌트에 속하는 모든 자식 컴포넌트 또한 렌더링 됩니다. 하지만 부모 컴포넌트에서 자식 컴포넌트로 내려주는 props가 바뀌지 않았다면, 해당 자식 컴포넌트를 리 렌더링 하지 않아도 될 것입니다. 컴포넌트에서 리 렌더링이 필요한 상황에서만 해주도록 설정을 할 수 있는데 이때 사용하는 함수가 바로 React.memo 함수입니다.

 

React.memo가 필요한 경우

1) 컴포넌트가 같은 props로 자주 렌더링 될때

2) 컴포넌트가 렌더링이 될때마다 복잡한 로직을 처리해야한다면

 

React.memo는 오직 Props 변화에만 의존하는 최적화 방법이다!

React.memo의 사용법

const Component = React.memo((props) => {
	return (/*컴포넌트 렌더링 코드*/)}
);
function UserList({ users, onRemove, onToggle }) {
  return (
    <div>
      {users.map(user => (
        <User
          user={user}
          key={user.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
}

export default React.memo(UserList);

=>props의 변화가 있을때만 렌더링을 허락해주고 props에 변화가 없다면 렌더링을 하지않고 이전의 이미 렌더링된 컴포넌트의 결과를 다시 재사용 하는것