在 BottomSheet 中集成分组列表:react-native-bottom-sheet 的 BottomSheetSectionList 实战指南 前端移动开发UI组件跨平台【免费下载链接】react-native-bottom-sheetA performant interactive bottom sheet with fully configurable options 项目地址https://gitcode.com/gh_mirrors/re/react-native-bottom-sheet点击查看免费下载BottomSheetSectionList是 react-native-bottom-sheetnpm 包名gorhom/bottom-sheet提供的一组“预集成”可滚动组件之一它将 React Native 原生SectionList与 BottomSheet 的手势系统深度打通让带分组头部Section Header的列表可以直接作为 BottomSheet 的内容使用而无需手动协调滚动与拖拽手势。读完本文你将掌握BottomSheetSectionList的全部 Props、被内部占用的属性Ignored Props、focusHook的正确用法以及它底层如何通过react-native-gesture-handler的Gesture.Native()与react-native-reanimated的动画滚动处理器实现滚动与面板拖拽的协同。组件定位预集成手势的 SectionList官方文档对它的定义非常简洁一个预集成了 BottomSheet 手势的 React NativeSectionList“A pre-integrated React Native SectionList with BottomSheet gestures”对应文档见 website/versioned_docs/version-4/components/bottomsheetsectionlist.md最新版文档位于 website/docs/components/bottomsheetsectionlist.md。它解决的核心问题是普通SectionList放进 BottomSheet 后列表自身的滚动与面板的拖拽会发生手势冲突——用户向上滚动列表时可能误触发面板收拢向下滚动到顶部时又无法把面板继续拖下来。BottomSheetSectionList通过内部的手势互操作simultaneous gesture与滚动状态机锁定/解锁把这两种交互统一管理起来。需要说明的是BottomSheet 的滚动体系是一整套设计除了BottomSheetSectionList还包含BottomSheetScrollView、BottomSheetFlatList、BottomSheetVirtualizedList与BottomSheetFlashListFlashList 集成全部由同一套工厂函数生成统一导出入口见 src/components/bottomSheetScrollable/index.ts。Props 总览继承自 React Native 的 SectionListPropsBottomSheetSectionList的 Props 类型定义如下见 src/components/bottomSheetScrollable/types.d.tsexport type BottomSheetSectionListPropsItemT, SectionT Omit Animated.AnimatePropsSectionListPropsItemT, SectionT, decelerationRate | scrollEventThrottle BottomSheetScrollableProps { ref?: RefBottomSheetSectionListMethods; };也就是说它继承 React NativeSectionList的全部 Propssections、renderItem、renderSectionHeader、stickySectionHeadersEnabled、keyExtractor等均可直接使用只是其中decelerationRate与scrollEventThrottle两个属性被Omit掉——这正是下文“Ignored Props”的来源。此外它还额外叠加了BottomSheetScrollableProps与ref透传。focusHook这是文档中重点强调的 Props当 BottomSheet 与多个可滚动组件一起使用时需要它来让 BottomSheet 检测当前的可滚动 ref尤其是在配合 React Navigation 使用时。你需要提供来自react-navigation/native的useFocusEffect。typedefaultrequiredfunctionReact.useEffectNOfocusHook的类型签名是(effect: EffectCallback, deps?: DependencyList) void见 src/components/bottomSheetScrollable/types.d.ts。从源码看它最终被传入useScrollableSetter见 src/hooks/useScrollableSetter.ts作为注册/注销“当前可滚动组件”的时机控制export const useScrollableSetter ( ref: React.RefObjectScrollable, type: SCROLLABLE_TYPE, contentOffsetY: SharedValuenumber, refreshable: boolean, useFocusHook useEffect ) { ... useFocusHook(handleSettingScrollable); };其中handleSettingScrollable会完成三件事把当前滚动偏移同步到底层共享值、把当前滚动类型SCROLLABLE_TYPE.SECTIONLIST写入状态、并通过findNodeHandle(ref.current)拿到节点 id 后调用setScrollableRef注册为“当前活动滚动组件”返回的清理函数则在失焦时调用removeScrollableRef注销。默认值为React.useEffect即组件每次挂载/卸载时注册一次。这在“BottomSheet 内只有一个滚动组件”的场景下完全够用。但当同一个 BottomSheet 内容区在多个页面或 Tab之间切换、出现多个滚动组件时仅仅依赖useEffect无法感知“当前哪个页面处于聚焦状态”BottomSheet 就可能抓到错误甚至已卸载的 ref。此时应当显式传入useFocusEffectimport { useFocusEffect } from react-navigation/native; BottomSheetSectionList ... focusHook{useFocusEffect} /这样每次页面获得焦点时都会重新注册对应滚动组件保证手势与滚动始终绑定在用户当前可见的列表上。仓库示例 example/src/components/contactList/ContactList.tsx 中的 SectionList 分支正是这么做的。其余扩展 PropsBottomSheetScrollableProps还包含两个属性v5 引入原 v4 文档未收录此处作为源码级补充enableFooterMarginAdjustmentboolean默认false为内容底部预留动画 Footer 的高度避免内容被 BottomSheetFooter 遮挡。scrollEventsHandlersHook默认useScrollEventsHandlersDefault自定义滚动事件处理 Hook官方标注为实验性功能签名可能在小版本中变更。Ignored Props为什么这些属性会被忽略文档明确指出以下 Props 即使传入了也会被忽略因为内部集成需要使用它们scrollEventThrottledecelerationRateonScrollBeginDrag结合 src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx 的实现可以看到三个属性的去向scrollEventThrottle组件内部在渲染ScrollableContainer时固定传入了scrollEventThrottle{16}以保证滚动事件以足够高的频率约 60fps驱动 Reanimated 共享值供面板手势状态判断使用。外部传入值会被这一固定值覆盖。decelerationRate组件通过useAnimatedProps根据滚动状态动态计算——当滚动组件处于UNLOCKED状态时使用平台原生的减速率iOS0.998、Android0.985处于LOCKED/UNDETERMINED状态时则为0映射表定义在 src/constants.tsconst SCROLLABLE_DECELERATION_RATE_MAPPER { [SCROLLABLE_STATE.UNDETERMINED]: 0, [SCROLLABLE_STATE.LOCKED]: 0, [SCROLLABLE_STATE.UNLOCKED]: Platform.select({ ios: 0.998, android: 0.985, default: 1, }), };onScrollBeginDrag滚动开始拖拽事件必须被内部处理器捕获见 src/hooks/useScrollHandler.ts用于在用户手指按下滚动的瞬间更新滚动锁定状态。内部会优先执行默认逻辑再通过runOnJS把事件转发给你传入的回调——但从类型层面它已被Omit因此外部无法再通过该属性挂接事件。完整示例下面这段示例来自官方文档与 v4 文档一致并补充了 v5 文档中的GestureHandlerRootView与enableDynamicSizing演示了在 BottomSheet 内渲染 10 个分组、每组 10 条数据的 SectionList并通过按钮调用snapToIndex/close控制面板import React, { useCallback, useRef, useMemo } from react; import { StyleSheet, View, Text, Button } from react-native; import { GestureHandlerRootView } from react-native-gesture-handler; import BottomSheet, { BottomSheetSectionList } from gorhom/bottom-sheet; const App () { // hooks const sheetRef useRefBottomSheet(null); // variables const sections useMemo( () Array(10) .fill(0) .map((_, index) ({ title: Section ${index}, data: Array(10) .fill(0) .map((_, index) Item ${index}), })), [] ); const snapPoints useMemo(() [25%, 50%, 90%], []); // callbacks const handleSheetChange useCallback((index) { console.log(handleSheetChange, index); }, []); const handleSnapPress useCallback((index) { sheetRef.current?.snapToIndex(index); }, []); const handleClosePress useCallback(() { sheetRef.current?.close(); }, []); // render const renderSectionHeader useCallback( ({ section }) ( View style{styles.sectionHeaderContainer} Text{section.title}/Text /View ), [] ); const renderItem useCallback( ({ item }) ( View style{styles.itemContainer} Text{item}/Text /View ), [] ); return ( GestureHandlerRootView style{styles.container} Button titleSnap To 90% onPress{() handleSnapPress(2)} / Button titleSnap To 50% onPress{() handleSnapPress(1)} / Button titleSnap To 25% onPress{() handleSnapPress(0)} / Button titleClose onPress{() handleClosePress()} / BottomSheet ref{sheetRef} index{1} snapPoints{snapPoints} enableDynamicSizing{false} onChange{handleSheetChange} BottomSheetSectionList sections{sections} keyExtractor{(i) i} renderSectionHeader{renderSectionHeader} renderItem{renderItem} contentContainerStyle{styles.contentContainer} / /BottomSheet /GestureHandlerRootView ); }; const styles StyleSheet.create({ container: { flex: 1, paddingTop: 200, }, contentContainer: { backgroundColor: white, }, sectionHeaderContainer: { backgroundColor: white, padding: 6, }, itemContainer: { padding: 6, margin: 6, backgroundColor: #eee, }, }); export default App;几个值得注意的实践点sections、snapPoints、渲染回调都包裹在useMemo/useCallback中避免列表在面板动画期间因父组件重渲染而产生不必要的刷新。面板初始停在index{1}即50%三个按钮分别把面板切换到 90%、50%、25%这与示例中的snapPoints顺序一一对应。如果整个应用尚未包裹GestureHandlerRootView需要像示例这样在根节点包一层仓库的模态示例中还提供了withGestureHandlerRoot辅助封装见 example/src/screens/withGestureHandlerRoot.tsx。v5 起 BottomSheet 默认启用动态尺寸enableDynamicSizing在固定snapPoints场景建议显式设为false避免内容尺寸参与吸点计算。源码纵深组件是如何被“预集成”的BottomSheetSectionList本身的实现非常薄见 src/components/bottomSheetScrollable/BottomSheetSectionList.tsx它先把原生SectionList包进Animated.createAnimatedComponent得到动画版本再交给统一的工厂函数最后用memo包裹并设置displayNameconst AnimatedSectionList Animated.createAnimatedComponentComponentPropstypeof RNSectionList( RNSectionList ); const BottomSheetSectionListComponent createBottomSheetScrollableComponent BottomSheetSectionListMethods, BottomSheetSectionListPropsnever, DefaultSectionT (SCROLLABLE_TYPE.SECTIONLIST, AnimatedSectionList); const BottomSheetSectionList memo(BottomSheetSectionListComponent); BottomSheetSectionList.displayName BottomSheetSectionList;真正的“预集成”逻辑全部集中在工厂函数 createBottomSheetScrollableComponent.tsx 中对 SectionList / FlatList / ScrollView 等全部复用核心机制包括1. 手势协同。组件从BottomSheetDraggableContext取出面板的拖拽手势draggableGesture并为自身创建一个Gesture.Native()原生手势与之simultaneousWithExternalGesture同步见 createBottomSheetScrollableComponent.tsx。这就是“列表滚动与面板拖拽可以同时进行”的实现基础。如果组件被用在 BottomSheet 之外且启用了内容拖拽工厂函数会直接throw报错提示Scrollable cannot be used out of the BottomSheet!见 createBottomSheetScrollableComponent.tsx。2. 滚动事件接管。组件通过useScrollHandlersrc/hooks/useScrollHandler.ts把onScroll、onBeginDrag、onEndDrag、onMomentumBegin/End全部接入手势处理器滚动偏移被写入共享值scrollableContentOffsetY供 Reanimated 在 UI 线程直接读取实现“列表滚到顶后再继续拖就是拖面板”的行为。3. 内容尺寸同步。通过useBottomSheetContentSizeSetter与onContentSizeChange把列表内容高度同步给面板见 createBottomSheetScrollableComponent.tsx支撑动态尺寸Dynamic Sizing能力。4. 平台容器。最终渲染由ScrollableContainer完成ScrollableContainer.tsx、Android 专用版本 ScrollableContainer.android.tsx内部传入scrollEventThrottle{16}、默认keyboardDismissModeinteractive、默认overScrollModenever并支持透传onRefresh/refreshControl以配合 pull-to-refresh 指南。暴露的实例方法由于类型上透传了ref你可以像使用普通SectionList一样调用其方法。BottomSheetSectionListMethods见 types.d.ts声明了scrollToLocation(params: SectionListScrollParams)滚动到指定 section 与 item 位置viewPosition为 0 时定位在顶部可能被粘性头部遮挡、1 在底部、0.5 居中。recordInteraction()通知列表发生了一次交互触发可见性计算配合waitForInteractions。flashScrollIndicators()短暂显示滚动指示器iOS。getScrollResponder()/getScrollableNode()获取底层滚动响应者与原生节点句柄。在实现上useImperativeHandle(ref, () scrollableRef.current)直接把内部useAnimatedRef指向的原生滚动组件实例暴露给外部见 createBottomSheetScrollableComponent.tsx因此这些方法实际上就是 React NativeSectionList的方法行为与原生一致。仓库中的真实使用案例示例应用的通讯录列表组件ContactList提供了最贴近生产的使用样板example/src/components/contactList/ContactList.tsx。其 SectionList 分支的关键配置包括BottomSheetSectionList {...rest} style{styles.container} contentContainerStyle{contentContainerStyle} stickySectionHeadersEnabled initialNumToRender{5} windowSize{10} maxToRenderPerBatch{5} bounces{true} sections{sections} keyExtractor{keyExtractor} renderSectionHeader{renderSectionHeader} renderItem{renderSectionItem} focusHook{useFocusEffect} removeClippedSubviews{Platform.OS android sections.length 0} /该案例把stickySectionHeadersEnabled开启以实现分组头部吸顶并为长列表设置了合理的initialNumToRender/windowSize/maxToRenderPerBatch来保证滚动性能focusHook{useFocusEffect}的用法与上文一致。通讯录的分组模拟数据由 example/src/utilities/createMockData.ts 中的createContactSectionsMockData生成。使用注意事项必须放在 BottomSheet 内部使用BottomSheetSectionList依赖 BottomSheet 提供的手势上下文与滚动状态脱离 BottomSheet 使用会直接抛错。底部安全区与 Footer示例中通过useSafeAreaInsets的bottom值给contentContainerStyle加paddingBottom若使用了 Footer可开启enableFooterMarginAdjustment自动避让。长列表性能分组数据量较大时建议沿用示例中的虚拟化参数windowSize、maxToRenderPerBatch、initialNumToRender并在 Android 上开启removeClippedSubviews。多滚动组件/React Navigation 场景务必通过focusHook传入useFocusEffect否则面板可能绑定错误的滚动 ref表现为手势错乱或列表无法拖拽。下拉刷新可直接透传onRefresh/refreshing/refreshControl使用参考 pull-to-refresh 指南 与 BottomSheetRefreshControl。相关阅读组件文档version-4 版 / 最新版滚动组件源码BottomSheetSectionList.tsx、createBottomSheetScrollableComponent.tsx、types.d.ts滚动状态机与常量src/constants.ts示例应用ContactList.tsx、BasicExamples.tsx其余滚动组件BottomSheetScrollView、BottomSheetFlatList、BottomSheetVirtualizedList、BottomSheetFlashList赞分享前端移动开发UI组件跨平台【免费下载链接】react-native-bottom-sheetA performant interactive bottom sheet with fully configurable options 项目地址https://gitcode.com/gh_mirrors/re/react-native-bottom-sheet点击查看免费下载相关推荐Telegraf NSDP Input 插件采集 Netgear 交换机端口流量的完整指南Telegraf NSDP Input 插件采集 Netgear 交换机端口流量的完整指南 Netgear Switch Discovery Protocol前端移动开发UI组件跨平台react-native-bottom-sheet 的 BottomSheetVirtualizedList预集成 VirtualizedList 的底部弹层滚动列表实战指南react native bottom sheet 的 BottomSheetVirtualizedList预集成 VirtualizedList 的底部弹层前端移动开发UI组件跨平台react-native-bottom-sheet与react-native-svg集成矢量图标在弹窗中的优化react native bottom sheet与react native svg集成矢量图标在弹窗中的优化 在移动应用开发中底部弹窗Bottom Sh前端移动开发UI组件跨平台上一篇如何快速打造你的专属AI虚拟伴侣Open-LLM-VTuber终极指南下一篇智能化解锁B站会员购三分钟掌握自动化抢票新体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考