React

useMemo, useCallback

김꼬알 2023. 7. 19. 12:48

useMemo

Memoization

  • 비용이 많이 드는 함수 호출의 결과를 저장하고 동일한 입력이 다시 발생할 때 캐시된 결과를 반환하여 컴퓨터 프로그램의 속도를 높이는데 주로 사용되는 최적화 기술
  • 이것을 적절하게 활용하면 중복 연산을 피할 수 있기 때문에 메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화 할 수 있음

 

function Component({a, b}) {
  const result = compute(a, b)
  return <div>{result}</div>
}
  • Component 내의 computer 함수가 만약 복잡한 연산을 수행하면 결과 값을 리턴하는데 오랜 시간이 걸림
  • 이럴 때 컴포넌트가 계속 리 렌더링 된다면 연산을 계속 수행하는데 오랜 시간이 걸려서 성능에 안 좋은 영향을 미치게 되고, UI 지연 현상도 일어나게 됨

 

  • 이러한 현상을 해결해주기 위해서 useMemo를 사용
  • compute 함수에 넘겨주는 a, b의 값이 이전과 동일하다면 컴포넌트가 리 렌더링 되더라도 연산을 다시 하지 않고 이전 렌더링 때 저장해두었던 값을 재활용하게 됨

 

  • useMemo는 메모이제이션된 값을 반환하는 함수
useMemo(() => fn, [deps]);

/* deps로 지정한 값이 변하게 된다면 () => fn 함수를 실행하고, 그 함수의 반환 값을 반환해줌
deps는 dependency의 약어로 의존성을 뜻하며, useMemo가 이 deps에 의존하고 있다는 것을 말함 */

 

  • useMemo 적용하기
// useMemo로 감싸준 후에 첫번째 인수에 의존성 배열에 compute 함수에서 사용하는 값을 넣어줌

function Component({a, b}) {
  const result = useMemo(() => compute(a, b), [a, b])
  return <div>{result}</div>
}

 

 

useCallback

  • useCallback은 메모이제이션된 함수를 반환함
useCallback(fn, [deps]);

// useCallback 또한 deps, 의존성이 있는 값이 변하면 fn에 등록한 함수를 반환함
useMemo(() => console.log(), [test]);
const memoizedCallback = useCallback(() => console.log(), [test]);

// useCallback이 함수를 반환하기 때문에, 그 함수를 가지는 const 변수에 초기화 하는 것이 일반적임

 

 

자식 컴포넌트에 props로 함수를 전달하는 경우

  • 컴포넌트에서 특정 함수를 정의할 경우 각각의 함수들은 모두 고유한 함수가 됨
  • 이런 고유한 함수가 생성될 경우, 부모를 통해 props에 함수를 전달받는 자식 컴포넌트에서는 props가 변경되었다고 판단해 리렌더링이 발생하게 됨

 

function App() {
  const [name, setName] = useState('');
  const onSave = () => {};

  return (
    <div className="App">
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <Profile onSave={onSave} />
    </div>
  );
}
  • useCallback을 사용하지 않을 경우, name이 변경되어 리렌더링이 발생하면 onSave함수가 새로 만들어지고, Profile 컴포넌트의 props로 onSave함수가 새로 전달됨
  • 이때, Profile 컴포넌트에서 useMemo를 사용해도 이전 onSave와 이후 onSave가 같은 값을 반환하지만 참조가 다른 함수가 되기 때문에 리렌더링이 발생

 

import React, { useCallback, useState } from 'react';
import Profile from './Profile';


function App() {
  const [name, setName] = useState('');
  const onSave = useCallback(() => {
    console.log(name);
  }, [name]);

  return (
    <div className="App">
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <Profile onSave={onSave} />
    </div>
  );
};
  • useCallback을 사용해서 onSave라는 함수를 재사용하는 것으로 자식 컴포넌트의 리렌더링을 방지할 수 있음

 

 

외부에서 값을 가져오는 api를 호출하는 경우

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

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  const fetchUser = () =>
    fetch(`https://your-api.com/users/${userId}`)
      .then((response) => response.json())
      .then(({ user }) => user);

  useEffect(() => {
    fetchUser().then((user) => setUser(user));
  }, [fetchUser]);

  // ...
}
  • fetchUser 함수가 변경될 때만 외부에서 api를 가져와 useEffect가 실행되어야 함
  • Profile이라는 컴포넌트가 리렌더링이 발생할 경우 fetchUser 함수에는 새로운 함수가 할당됨
  • useEffect()함수가 호출되어 user 상태 값이 바뀌고, state 값이 바뀌었기 때문에 다시 리렌더링이 일어나게 되므로 무한 루프에 빠지게 됨

 

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

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  const fetchUser = useCallback(
    () =>
      fetch(`https://your-api.com/users/${userId}`)
        .then((response) => response.json())
        .then(({ user }) => user),
    [userId]
  );

  useEffect(() => {
    fetchUser().then((user) => setUser(user));
  }, [fetchUser]);

  // ...
}
  • useCallback을 사용하면 fetchUser 함수의 참조값을 동일하게 유지시킬 수 있음
  • api 옵션으로 사용되는 userId가 변동될 때만 fetchUser에 새로운 함수가 할당되도록 설정하고, 그것이 아니면 동일한 함수가 실행되게 되어 무한 루프에 빠지지 않도록 할 수 있음

'React' 카테고리의 다른 글

Test Driven Development, React Testing Library  (0) 2023.08.01
React Router Dom, APIs  (0) 2023.07.28
불변성 지키기  (0) 2023.07.11
State, Props  (0) 2023.07.04
React Hooks  (0) 2023.07.03