My Boundary As Much As I Experienced

리액트에서는 많이 사용하지 않는 useCallback, 왜 React Native에선 주로 사용할까? (+useCallback 사용 상황) 본문

FrontEnd/React Native

리액트에서는 많이 사용하지 않는 useCallback, 왜 React Native에선 주로 사용할까? (+useCallback 사용 상황)

Bumang 2024. 5. 11. 23:11

공부하게 된 계기

사수님의 코드도 그렇고 지금 듣는 RN강의도 그렇고 핸들러를 만들 때 useCallback을 많이 쓴다.

 

기존에 내가 알고있던 상식으론 useCallback은 컴포넌트가 재렌더되어도

함수들이 재생성 되지 않게 도와주는 훅으로 알고있다.

 

뭐.. 쓰면 좋겠지. 그러나 웹개발에서 굳이 모든 핸들러에다가 useCallback을 두르진 않는다.

렌더가 너무 빈번한게 아니라면 함수를 메모이제이션 해놓는 메모리가 더 손해라고 보기도 하니까.

 

그런데 왜 React Native 앱개발에선 useCallback을 되게 많이 쓸까?

그게 궁금해서 찾아보게 되었다.

일단 useCallback을 일반적으로 왜 쓰는지부터 알아보자.

 

useCallback을 쓰는 경우

  1. 컴포넌트 리렌더링 최적화: 콜백 함수를 useCallback으로 래핑하면 의존성 배열에 지정한 값들이 변경되지 않는 한 동일한 함수 인스턴스를 재사용한다. 이는 자식 컴포넌트에 콜백 함수를 props로 전달할 때 불필요한 리렌더링을 방지할 수 있다.
  2. 성능 최적화: useCallback을 사용하면 함수가 불필요하게 재생성되는 것을 방지하여 성능을 최적화할 수 있다. 특히 렌더링 비용이 높은 컴포넌트나 복잡한 상태 관리가 필요한 경우 유용하다.

 

useCallback 활용 예제: 핸들러를 props로 하위 컴포넌트에 줄 때

예제를 통해 useCallback을 사용하는 방법을 살펴보자.

import React, { useState, useCallback } from 'react';
import { Button, View, Text } from 'react-native';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="Increment" onPress={increment} />
    </View>
  );
};

export default MyComponent;
// Button.tsx
// memo로 재생성을 막아둔 상태이다. 
// useCallback이 없었다면 increment 계속 발생해서 Button.tsx도 재렌더됐을 것이다.
const Button = React.memo(({ increment }) => {
  console.log('ChildComponent rendered');
  return <button onClick={increment}>Click me</button>;
});

 

위 코드를 분석해보자. 일단 increment에 useCallback을 두른걸 알 수 있다.

그리고 이 increment 함수를 Button에 제공하고, Button을 클릭할 때 마다 더하기 1을 수행하게 했다고 하자.

 

이때, useCallback을 안 썼으면 count 더하기 1 할 때 마다 MyComponent의 state가 변경되고,

MyComponent는 다시 렌더될 것이다. 그렇게 되면 increment 핸들러도 다시 생성될 것이고,

increment를 props로 받는 하위 컴포넌트 Button까지 재렌더하게 된다.

 

그러나 increment를 useCallback으로 감싸므로 재생성을 막아 Button의 재렌더를 막을 수 있다.

그러면 일반 React와 ReactNative는 어떤 환경 차이가 있길래 useCallback활용도가 갈리는걸까?

 

일반 React에서 useCallback 사용

일반 React에서 useCallback은 주로 성능 최적화가 필요한 때가 있다.

그러나 모든 경우에 useCallback을 사용할 필요는 없다.

함수가 가볍거나, 렌더링 비용이 낮은 컴포넌트에서는

useCallback을 사용하지 않아도 큰 문제가 되지 않는다.

 

React Native에서 useCallback 사용의 중요성

그러나 앞서 말한대로 React Native에서는 useCallback을 더 자주 사용하는 경향이 있는데,

그 이유는 주로 모바일 환경에선 성능 최적화에 더욱 신경을 써야하기 때문이다.

특히 네이티브 앱이 아니라 브릿지라는 중간다리를 통해 Native이벤트를 조작하는 RN 특성 상 더더욱 필요하다.

  1. 애니메이션과 제스처: React Native는 애니메이션과 제스처 처리가 많다. 이때 핸들러 함수가 자주 호출되거나, 자식 컴포넌트에 전달될 경우 불필요한 리렌더링을 방지하기 위해 useCallback이 유용하다.
  2. 성능 문제: 모바일 환경에서는 성능 최적화가 더 중요하다. 제한된 리소스(배터리, CPU 등)를 효율적으로 사용하기 위해 불필요한 렌더링을 최소화하는 것이 중요하다. useCallback은 함수 재생성을 방지하여 성능을 개선할 수 있다.
  3. 리렌더링 비용: React Native의 뷰 컴포넌트는 일반 React DOM 요소보다 리렌더링 비용이 클 수 있다. 따라서 useCallback을 사용하여 불필요한 리렌더링을 피하는 것이 더 큰 차이를 만들 수 있다. 그리고 Native 앱보다 React Native는 항상 성능 문제에 더욱 예민해야된다는 것을 명심해야된다.
  4. 비동기 작업: React Native는 비동기 작업을 자주 사용한다. 네트워크 요청, 파일 시스템 접근 등 다양한 비동기 작업에서 핸들러 함수의 재생성을 최소화하는 것이 중요하다.
  5. Stack: 리액트 네이티브에서는 주로 React Navigation 라이브러리를 router로 활용한다. 그중 Stack구조를 빈번하게 사용하게 되는데 이는 말 그대로 페이지를 쌓는 방식으로 여러 페이지의 상태를 보존한다. 이때 가장 루트에 가까운 스크린의 state를 한 5번째 쯤의 페이지에서 변경했다고 해보자. 그러면 한꺼번에 다섯 스크린이 재렌더 되는 것이다... 이런 비용을 없애기 위해 useCallback이 필요하다.

 

정리 후기?

그렇다. 나는 브라우저보다 요새 스마트폰이 더 강력할 줄 알았는데 아직 모바일 환경이 pc브라우저 환경보단 성능 이슈가 더 있나보다. 그리고 ReactNative는 Native앱들보다 더더욱 성능최적화에 신경을 써야되기도 하고.. 모바일 페이지의 stack구조도 최적화의 중요성을 부각시킨다는 것을 깨달았다. 다음글엔 useCallback 정리하는 김에 useMemo와 React.memo도 공부해봤는데 이걸 올려봐야겠다.