[JS] Component children으로 화살표 함수가 선언되는 경우

2023. 5. 9. 16:27React Native


영상 다운로드 시에, BottomSheet 내부의 컴포넌트가 반복적으로 리렌더링되는 에러를 발견하였다.

 

 

 

 

 

리렌더링 되는 부분의 코드는 다음과 같다.

 <BottomSheet
    ref={sheetRef}
    index={0}
    style={{
      backgroundColor: AppColors.albumInfoBackgroundColor,
    }}
    snapPoints={snapPoints}
    animateOnMount={false}
    containerHeight={screenHeight - containerHeight * 0.25}
    onChange={index => setSheetStatus(Math.max(index, 0))}
    handleComponent={() => <AlbumInformationBackground />}
    backgroundComponent={() => <AlbumInformationBackground />}
    children={ () => (
          <AlbumInformationBody
            swiperRef={newAlbum.swiperRef}
            layoutHeight={layoutHeight}
            sheetRef={sheetRef}
            currentIndex={currentIndex}
            album={album}
            sheetStatus={sheetStatus}
            setSheetStatus={setSheetStatus}
            newAlbum={newAlbum}
            setVisible={setVisible}
          />
      )
    }
/>

문제가 되는 부분은 컴포넌트의 children이 인스턴스가 없는 화살표 함수로 선언되어 있는 부분이다.

 

 

 

<BottomSheet>
	<ChildrenComponent />
</BottomSheet>

첫번째로 이상한 점은, children은 기본 prop이기 때문에 위와 같이 넘겨주는 것이 관행인데
굳이 prop전달문 안에 children = { } 형식으로 넘겨주는 이유가 불분명하다.

 

 

 

두 번째로 이상한 점은, 굳이 왜 화살표 함수의 형태로 넘겨주는가 하는 점이다.

문제가 되는 children prop 위에 있는 handleComponent, backgroundComponent가 모두 화살표 함수로 선언되어 있는 것은 다음과 같은 이유에서 이다.

handleComponent의 타입은 FunctionComponent로 선언되어 있기 때문에 컴포넌트 element가 assign될 수 없다.


화살표 함수는 보통 React 혹은 React-Native에서 onClick과 같은 이벤트 핸들링 메소드에서 사용된다.
이벤트 메소드를 컴포넌트의 props으로 전달할때,

<Button onClick={hadleButtonClick()}>Click!</button>

위와같이 직접적으로 JSX 함수를 호출한다면 클릭 이벤트와 상관없이 바로 실행된다.

이것은 함수의 반환값이 onClick attribute에서 사용된다는 의미이기 때문이다.

따라서 버튼을 클릭했을때 prop으로 넘긴함수를 호출하지 않고 리렌더링 될 때 한 번만 호출된다.

즉, 버튼 클릭에 따른 이벤트 처리 액션은 일어나지 않는 것이다.

 

 

 

때문에 이와 같은 경우  다음과 같이 함수명만 기재하거나, 화살표 함수로 호출될 함수를 넘긴다.

<Button onClick={hadleButtonClick}>Click!</button>
// or
<Button onClick={() => { hadleButtonClick() }}>Click!</button>

 

 

 

 

그러나 화살표 함수를 컴포넌트값을 기대하는 prop에 넣어버리는 경우는 문제가 발생한다.

컴포넌트를 렌더링하기 위해서는 해당 컴포넌트가 인스턴스화 되어야 한다.

React-Native 컴포넌트의 자식들이 인스턴스 없이 화살표 함수로 선언되면 컴포넌트가 렌더링될 때마다 화살표 함수가 재선언되어 성능에 영향을 미친다.

따라서 다음과 같이 코드를 변경해 주었다.

<BottomSheet
    ref={sheetRef}
    index={0}
    style={{
      backgroundColor: AppColors.albumInfoBackgroundColor,
    }} 
    snapPoints={snapPoints}
    animateOnMount={false}
    containerHeight={screenHeight - containerHeight * 0.25} 
    onChange={index => setSheetStatus(Math.max(index, 0))}
    handleComponent={() => <AlbumInformationBackground />}
    backgroundComponent={() => <AlbumInformationBackground />}>
    <AlbumInformationBody
      swiperRef={newAlbum.swiperRef}
      layoutHeight={layoutHeight}
      sheetRef={sheetRef}
      currentIndex={currentIndex}
      album={album}
      sheetStatus={sheetStatus}
      setSheetStatus={setSheetStatus}
      newAlbum={newAlbum}
      setVisible={setVisible}
    />
</BottomSheet>

 

또는 다음과 같이 화살표 함수 부분만 제거해도 바르게 동작한다.

 <BottomSheet
    ref={sheetRef}
    index={0}
    style={{
      backgroundColor: AppColors.albumInfoBackgroundColor,
    }}
    snapPoints={snapPoints}
    animateOnMount={false}
    containerHeight={screenHeight - containerHeight * 0.25}
    onChange={index => setSheetStatus(Math.max(index, 0))}
    handleComponent={() => <AlbumInformationBackground />}
    backgroundComponent={() => <AlbumInformationBackground />}
    children={
      <AlbumInformationBody
        swiperRef={newAlbum.swiperRef}
        layoutHeight={layoutHeight}
        sheetRef={sheetRef}
        currentIndex={currentIndex}
        album={album}
        sheetStatus={sheetStatus}
        setSheetStatus={setSheetStatus}
        newAlbum={newAlbum}
        setVisible={setVisible}
      />
    }
/>



다음은 React/React-Native에서 화살표 함수를 사용하기 위한 몇 가지 팁이다:

 

  • 상태 저장할 필요가 없는 짧고 간단한 함수에만 화살표 함수를 사용하라.
  • 함수를 state에 저장해야 하는 경우 화살표 함수 대신 클래스 컴포넌트를 사용하거나 별도의 const 상수로 선언하여 사용하라.
  • 길거나 복잡한 화살표 함수를 사용해야 하는 경우 컴포넌트 외부에서 선언된 함수로 감싸라.

그러면 앱 성능에 영향을 주지 않고 화살표 함수를 사용할 수 있다.

 


 

 

 

혹은 지속적으로 발생하는 리렌더링이 문제가 되는 경우에, React.memo를 사용하여 함수 컴포넌트를 메모이제이션 할 수 있다.

메모이제이션은 컴포넌트를 이전에 렌더링된 결과를 캐시하여 성능을 최적화하는 기술아다.

React.memo는 함수 컴포넌트를 래핑하여 컴포넌트의 props가 변경되지 않으면 이전에 렌더링된 결과를 사용하도록 한다.

예를 들어, 다음과 같은 함수 컴포넌트가 있다고 해보자.

const MyComponent = ({ children }) => {
  return (
    <View>
      {children}
    </View>
  );
};​

이 경우, React.memo를 사용하여 함수 컴포넌트를 메모이제이션 할 수 있다.

const MyComponent = React.memo(({ children }) => {
  return (
    <View>
      {children}
    </View>
  );
});

이제 이 함수 컴포넌트는 children을 렌더링할 때 인스턴스화되어 발생하는 문제를 방지할 수 있다.

 

 

혹은 컴포넌트 내에서 useMemo()를 사용하여 최적화 할 수도 있다.

import React, { useMemo } from 'react';

function MyComponent({ data }) {
  const filteredData = useMemo(() => {
    return data.filter(item => item.value > 10);
  }, [data]);

  return (
    <div>
      {filteredData.map(item => (
        <p key={item.id}>{item.label}: {item.value}</p>
      ))}
    </div>
  );
}

위 코드에서는 useMemo를 사용하여 filteredData라는 상수를 만들었다.

이 상수는 data가 변경되지 않았을 때 이전에 계산된 값을 재사용한다.

useMemo는 data 배열이 변경될 때만 함수를 다시 실행하므로 불필요한 리렌더링을 방지할 수 있다.