앱에서 바텀 시트는 생각보다 자주 필요하다. 필터, 상세 옵션, 공유 메뉴... 아래에서 스윽 올라오는 그 시트 말이다. 처음엔 Modal에 Animated로 대충 흉내를 냈는데, 드래그로 닫기, 중간 높이에서 멈추기(스냅), 키보드 대응까지 붙이려니 코드가 금방 지저분해졌다.
그러다 @gorhom/bottom-sheet로 갈아탔다. Reanimated 기반이라 애니메이션이 JS 스레드 안 타고 부드럽고, 스냅 포인트나 제스처 처리를 라이브러리가 다 해준다.
설치
Reanimated와 gesture-handler에 의존한다. Expo면 이렇게.
npx expo install @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler
앱 최상단을 GestureHandlerRootView로 감싸야 제스처가 먹는다. 이거 빼먹으면 시트가 열리긴 하는데 드래그가 안 돼서 한참 헤맨다.
// App.tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<RootNavigator />
</GestureHandlerRootView>
);
}
기본 사용
BottomSheetModal을 쓰면 화면 어디서든 ref로 열고 닫을 수 있다. snapPoints는 시트가 멈출 높이 목록이다. 퍼센트로 주는 게 편하다.
import { useRef, useMemo, useCallback } from 'react';
import { View, Text, Button } from 'react-native';
import {
BottomSheetModal,
BottomSheetView,
BottomSheetModalProvider,
} from '@gorhom/bottom-sheet';
function FilterScreen() {
const sheetRef = useRef<BottomSheetModal>(null);
const snapPoints = useMemo(() => ['30%', '60%'], []);
const open = useCallback(() => sheetRef.current?.present(), []);
return (
<View style={{ flex: 1 }}>
<Button title="필터 열기" onPress={open} />
<BottomSheetModal ref={sheetRef} snapPoints={snapPoints}>
<BottomSheetView style={{ padding: 24 }}>
<Text style={{ fontSize: 18 }}>정렬 기준</Text>
</BottomSheetView>
</BottomSheetModal>
</View>
);
}
BottomSheetModal을 쓸 거면 앱 상단을 BottomSheetModalProvider로도 감싸줘야 한다. present()로 열고 dismiss()로 닫는다. 뒤 배경을 어둡게 깔고 싶으면 backdropComponent에 BottomSheetBackdrop을 넘기면 된다.
스크롤이 필요하면
시트 안에 긴 목록을 넣을 땐 그냥 FlatList를 쓰면 스크롤과 시트 드래그가 서로 싸운다. 라이브러리가 감싼 BottomSheetFlatList, BottomSheetScrollView를 쓰면 위로 스크롤이 끝났을 때 자연스럽게 시트 드래그로 넘어간다.
import { BottomSheetFlatList } from '@gorhom/bottom-sheet';
<BottomSheetFlatList
data={options}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Row {...item} />}
/>
정리
Modal로 직접 만들 때 제일 골치 아팠던 게 드래그로 닫기와 스냅 처리였는데, 이 라이브러리는 그걸 기본으로 준다. GestureHandlerRootView로 감싸는 것, 그리고 시트 안 스크롤은 전용 컴포넌트를 쓴다는 것 — 이 두 개만 안 빼먹으면 세팅은 10분이면 끝난다.