React & React Native

React-Native-Bottom-Sheet 사용법

ghorid 2024. 12. 16. 22:39

 

리액트 네이티브에서 bottom sheet을 사용하는 방법을 작성하고자 한다. 

 

Bottom Sheet란?

Bottom Sheet란 사진처럼 아래에서 올라오는 컴포넌트를 의미한다. UX를 고려하여 thumb zone에 맞게 적절한 높이를 지니고 있으며 다양한 앱 및 웹 서비스에 쉽게 볼 수 있는 컴포넌트이다.

 

이를 별도 라이브러리 없이 구현하고자 하였으나, 여러 문제가 발생했고 npm 통계 상 라이브러리가 매우 자주 사용되고 있음을 확인하여 성능에 문제가 없겠다 싶어 라이브러리를 적용하기로 했다. 

https://www.npmjs.com/package/@gorhom/bottom-sheet

 

@gorhom/bottom-sheet

A performant interactive bottom sheet with fully configurable options 🚀. Latest version: 5.0.6, last published: a month ago. Start using @gorhom/bottom-sheet in your project by running `npm i @gorhom/bottom-sheet`. There are 187 other projects in the np

www.npmjs.com

 

 

https://gorhom.dev/react-native-bottom-sheet/

 

Bottom Sheet | React Native Bottom Sheet

A performant interactive bottom sheet with fully configurable options 🚀

gorhom.dev

더 자세한 사항은 위의 공식 문서를 따라가면 된다.

 

여러 블로그 글이 있었지만 이전 버전인 것인지, 최신 버전의 적용 방법과 조금 다른 부분이 있어 항상 공식 문서를 따라하는 걸 추천한다. 

특히 이 부분이 빠지면. 사용할 수 없으니 꼭 읽고 적용하길 바란다.

 

 

 

사용 방법

import React, { useCallback, useMemo, useRef } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';

const App = () => {
  // ref
  const bottomSheetRef = useRef<BottomSheet>(null);

  // callbacks
  const handleSheetChanges = useCallback((index: number) => {
    console.log('handleSheetChanges', index);
  }, []);

  // renders
  return (
    <GestureHandlerRootView style={styles.container}>
      <BottomSheet
        ref={bottomSheetRef}
        onChange={handleSheetChanges}
      >
        <BottomSheetView style={styles.contentContainer}>
          <Text>Awesome 🎉</Text>
        </BottomSheetView>
      </BottomSheet>
    </GestureHandlerRootView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'grey',
  },
  contentContainer: {
    flex: 1,
    padding: 36,
    alignItems: 'center',
  },
});

export default App;

먼저 GestureHandlerRootView를 감싼 후 flex:1로 화면을 꽉 채워주어야 한다. 

 

이후 BottomSheet를 호출하여 사용하면 된다.

 

 

이 중 내가 직접 사용한 props를 보여주고자 한다. 

  const [bottomSheetState, setBottomSheetState] = useState(-1);


  const renderBackdrop = useCallback(
    (props: any) => (
      <BottomSheetBackdrop
        {...props}
        disappearsOnIndex={-1} // index가 -1일 때 배경 사라짐
        appearsOnIndex={0} // index 0 이상일 때 배경 등장
        opacity={0.5} // 배경 어두움 정도 설정
      />
    ),
    [],
  );
  
  const handleSheetChanges = useCallback((index: number) => {
    setBottomSheetState(index);
  }, []);

return (
    <BottomSheet
      ref={bottomSheetRef}
      onChange={handleSheetChanges}
      enablePanDownToClose={true}
      enableDynamicSizing={false} // 이게 있어야 0번 인덱스가 내가 설정한 40%로 설정됨
      index={bottomSheetState}
      snapPoints={['40%', '80%']}
      backdropComponent={renderBackdrop} // Backdrop 추가
    >
      {children}
    </BottomSheet>
  );

 

index : Bottom Sheet의 열린 상태를 index로 제어한다. -1이면 닫힌 상태, 0 이상이면 열린 상태이다. 초기값을 -1로 두면 닫힌 상태에서 시작하고 0으로 두면 열린 상태에서 시작한다. 

 

snapPoints: Bottom Sheet의 위치를 지정한다. 이는 index와 함께 쓰이는데 snapPoinst에 지정된 배열에 따라 index가 0번이면 ['40%' , '80%'] 에서 40% 높이만큼 올라오고 index가 1번이면 80%로 올라온다. 픽셀 값으로 숫자만 넣어도 되고 퍼센트로 지정할 거면 string이어야 한다.

 

enableDynamicSizing: snapPoints, index를 제대로 지정해도 뭔가 이상한 게 있을 것이다. 바로 내가 지정하지 않은 위치에 Bottom Sheet가 위치하는 경우가 생긴다.
이는 Bottom Sheet 자체에서 미리 지정한 값이 snapPoints에 추가되어 사용되기 때문이다. 이는 픽셀 100의 값이 0번 인덱스에 추가되는데 ['40%' , '80%']으로 지정했으면 [100, '40%' , '80%']으로 사용되는 것이다. 그래서 index가 0번이면 내가 직접 지정한 40%가 아닌 100의 위치에 있는 것이다. 이를 사용하지 않기 위해서는 enableDynamicSizing를 꼭 false값으로 지정해야한다. 

 

enablePanDownToClose : BottomSheet를 아래로 내리면 Bottom Sheet가 닫히게 된다. 

 

 

대충 이정도로만 정리하면 충분히 원하는 bottom sheet를 구현할 수 있다.