☰
react-native-bottom-sheet 集成指南:用 BottomSheetFlashList 构建可手势联动的 FlashList 高性能列表
2026/9/25 3:09:53 网站建设 项目流程
  • 前端
  • 移动开发
  • UI组件
  • 跨平台

【免费下载链接】react-native-bottom-sheet

A performant interactive bottom sheet with fully configurable options 🚀

项目地址:https://gitcode.com/gh_mirrors/re/react-native-bottom-sheet
点击查看免费下载

BottomSheetFlashList是 react-native-bottom-sheet 提供的预集成滚动组件,它在@shopify/flash-list的FlashList之上无缝接入了BottomSheet的拖拽手势、滚动状态与吸附(snap)逻辑,是需要在底部弹层中渲染大量列表数据的首选方案。本文将以 website/docs/components/bottomsheetflashlist.md 为主线,结合仓库源码与官方示例,完整讲解它的 Props、安装方式、与 React Navigation 的配合技巧以及底层实现原理,读完即可在项目中落地一个高性能的底部弹层列表。

一、什么是 BottomSheetFlashList

BottomSheetFlashList是 react-native-bottom-sheet 中与BottomSheetScrollView、BottomSheetFlatList、BottomSheetSectionList、BottomSheetVirtualizedList并列的预集成滚动组件,统一从 src/components/bottomSheetScrollable/index.ts 导出。其官方定位是:

A pre-integratedFlashListcomponent withBottomSheetgestures.

即:一个已经与BottomSheet手势深度集成的FlashList。它直接继承了FlashListProps,因此你熟悉的data、renderItem、keyExtractor、estimatedItemSize、onEndReached、ListFooterComponent、ItemSeparatorComponent等所有 FlashList 能力都原样可用,无需额外适配。

一个典型的代码形态是:

import BottomSheet, { BottomSheetFlashList } from "@gorhom/bottom-sheet"; <BottomSheet ref={sheetRef} snapPoints={snapPoints} enableDynamicSizing={false}> <BottomSheetFlashList data={data} keyExtractor={keyExtractor} renderItem={renderItem} estimatedItemSize={43.3} /> </BottomSheet>

二、安装前置条件:必须先安装 @shopify/flash-list

需要特别强调的是,@shopify/flash-list并不是react-native-bottom-sheet 的强制依赖。在 src/components/bottomSheetScrollable/BottomSheetFlashList.tsx 的源码中,库采用了 Metro 的可选导入(optional import)机制:

let FlashList: { FlashList: React.FC }; try { FlashList = require('@shopify/flash-list') as never; } catch (_) {}

当检测不到@shopify/flash-list时,组件会在渲染期直接抛出错误提示安装:

useMemo(() => { if (!FlashList) { throw 'You need to install FlashList first, `yarn install @shopify/flash-list`'; } }, []);

因此在你的项目中,必须先自行安装 FlashList,例如:

yarn install @shopify/flash-list

官方示例仓库中锁定的是@shopify/flash-list@1.7.1(见 example/package.json),可供参考。安装完成后,BottomSheetFlashList会将renderScrollComponent注入 FlashList 内部,把实际滚动载体替换为BottomSheetScrollView,从而让 FlashList 获得 bottom sheet 的手势上下文。

三、Props 详解

BottomSheetFlashList的 Props 由两部分组成:FlashList 原生 Props + bottom sheet 专有的滚动配置。

3.1 继承 FlashListProps

组件类型定义继承了 FlashList 的全部 Props(见 src/components/bottomSheetScrollable/BottomSheetFlashList.tsx),唯一被剥离的是decelerationRate、onScroll、scrollEventThrottle这三个属性——因为它们由 bottom sheet 的滚动状态驱动,以实现解锁/锁定滚动时不同减速率的原生手感。其余如:

  • data/renderItem/keyExtractor
  • estimatedItemSize(FlashList 高性能的关键,务必提供)
  • onEndReached/ListFooterComponent/ListEmptyComponent
  • ItemSeparatorComponent/viewabilityConfig

均按 FlashList 语义原样使用。

3.2 focusHook

属性类型默认值必填
focusHookfunctionReact.useEffect否

官方文档原话:当 bottom sheet 与多个滚动组件(multiple scrollables)一起使用时,需要该属性来让 bottom sheet 正确识别当前的滚动 ref,尤其是在配合 React Navigation 使用时。

原理层面,滚动 ref 的注册逻辑位于 src/hooks/useScrollableSetter.ts,其签名中的最后一个参数就是 focus hook:

export const useScrollableSetter = ( ref, type, contentOffsetY, refreshable, useFocusHook = useEffect ) => { // ... useFocusHook(handleSettingScrollable); };

handleSettingScrollable内部会通过findNodeHandle拿到滚动节点的原生 id,并调用setScrollableRef将其注册为 bottom sheet 当前的活动滚动源,同时把滚动偏移、滚动类型、是否可下拉刷新等状态同步到共享值上。默认使用useEffect意味着组件只要挂载就会被注册;但当多个页面/弹层各自持有滚动组件时,非焦点页面的滚动组件也可能抢占注册,导致手势联动错乱。

此时应从@react-navigation/native引入useFocusEffect并传入,让注册行为与页面/弹层获得焦点同步:

import { useFocusEffect } from '@react-navigation/native'; <BottomSheetFlashList focusHook={useFocusEffect} /* ... */ />

仓库示例 example/src/components/contactList/ContactList.tsx 中便以focusHook={useFocusEffect}的方式将ContactList内的滚动列表接入 bottom sheet,可作为真实场景的对照实现。

3.3 其他继承的滚动专有 Props

在 src/components/bottomSheetScrollable/types.d.ts 中,BottomSheetScrollableProps还定义了以下两个属性,同样适用于BottomSheetFlashList:

  • enableFooterMarginAdjustment?: boolean(默认false):自动为滚动内容底部增加 margin,避免内容被动画 footer 遮挡。开启后,底层实现会通过useAnimatedStyle将animatedFooterHeight.value映射为容器的marginBottom(见 src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx)。
  • scrollEventsHandlersHook?: ScrollEventsHandlersHookType(默认useScrollEventsHandlersDefault):自定义滚动事件处理钩子,可用于高级定制滚动行为。这是一个实验性 API,签名可能在主版本内变动。

四、完整示例

以下代码来自官方文档,可在你的 App 中直接运行——它演示了 50 条字符串数据、双吸附点(25% / 50%)以及通过 ref 控制吸附与关闭的完整闭环:

import React, { useCallback, useRef, useMemo } from "react"; import { StyleSheet, View, Text, Button } from "react-native"; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import BottomSheet, { BottomSheetFlashList } from "@gorhom/bottom-sheet"; const keyExtractor = (item) => item; const App = () => { // hooks const sheetRef = useRef<BottomSheet>(null); // variables const data = useMemo( () => Array(50) .fill(0) .map((_, index) => `index-${index}`), [] ); const snapPoints = useMemo(() => ["25%", "50%"], []); // callbacks const handleSnapPress = useCallback((index) => { sheetRef.current?.snapToIndex(index); }, []); const handleClosePress = useCallback(() => { sheetRef.current?.close(); }, []); // render const renderItem = useCallback(({ item }) => { return ( <View key={item} style={styles.itemContainer}> <Text>{item}</Text> </View> ); }, []); return ( <GestureHandlerRootView style={styles.container}> <Button title="Snap To 50%" onPress={() => handleSnapPress(1)} /> <Button title="Snap To 25%" onPress={() => handleSnapPress(0)} /> <Button title="Close" onPress={() => handleClosePress()} /> <BottomSheet ref={sheetRef} snapPoints={snapPoints} enableDynamicSizing={false} > <BottomSheetFlashList data={data} keyExtractor={keyExtractor} renderItem={renderItem} estimatedItemSize={43.3} /> </BottomSheet> </GestureHandlerRootView> ); }; const styles = StyleSheet.create({ container: { flex: 1, paddingTop: 200, }, contentContainer: { backgroundColor: "white", }, itemContainer: { padding: 6, margin: 6, backgroundColor: "#eee", }, }); export default App;

要点提示:

  • 示例中enableDynamicSizing={false}与显式声明snapPoints搭配,保证 FlashList 的尺寸测量不被动态内容高度干扰;
  • estimatedItemSize是 FlashList 性能的关键参数,应填列表中常规行高的近似值(示例 43.3 为每行 padding/margin 相加所得),提供后 FlashList 才能跳过首轮全量测量、直接复用估算的布局;
  • 列表必须位于BottomSheet内部才能拿到手势上下文——见下文源码原理。

五、源码原理:它如何与 BottomSheet 手势联动

BottomSheetFlashList之所以能"听懂" bottom sheet 的拖拽,关键在 src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx 这个工厂函数。从源码结构看,所有BottomSheet*滚动组件都经由它生成,其核心链路如下:

  1. 原生手势并联:组件通过useContext(BottomSheetDraggableContext)取到 bottom sheet 的拖拽手势(draggableGesture),然后用Gesture.Native().simultaneousWithExternalGesture(draggableGesture)把滚动原生的 pan 手势与弹层拖拽手势声明为"同时进行"的关系(源码 L88-L97)。因此手指既可以在列表内部滚动,也可以在列表顶部继续把整个 sheet 往下拖。

  2. 滚动状态同步:通过useScrollHandler捕获onScroll,将列表的 contentOffset 写入共享值scrollableContentOffsetY;同时由useAnimatedProps根据 bottom sheet 的滚动状态(SCROLLABLE_STATE.UNLOCKED等)实时切换decelerationRate与滚动指示器显隐(源码 L77-L86),实现"滚动到底后继续拖拽弹层"时截然不同的物理反馈。

  3. 内容尺寸上报:handleContentSizeChange通过setContentSize把内容高度回传,供 bottom sheet 的动态尺寸(dynamic sizing)计算使用(源码 L101-L109)。

  4. 滚动容器包装:最终渲染由 ScrollableContainer.tsx 完成,它把BottomSheetDraggableScrollable包在 FlashList 外层,将手势上下文传递到列表内部。

值得一提的还有其受控解构:BottomSheetFlashListComponent会把 bottom sheet 专有的focusHook、scrollEventsHandlersHook、enableFooterMarginAdjustment从 props 中剥离,仅将这些能力注入到内部替换的BottomSheetScrollView中,其余 props 原样透传给 FlashList(见 BottomSheetFlashList.tsx)。

六、实战进阶:仿 Twitter 时间线的分页加载示例

仓库示例 example/src/screens/integrations/flashlist/FlashListExample.tsx 提供了一个更贴近真实业务的数据流示例:底部弹层内渲染推文时间线,支持 25% / 50% / 90% 三档吸附,并演示了 FlashList 的典型进阶能力:

  • onEndReached分页加载:滚动到底部时延时 1s 再追加 10 条推文(setTweets([...tweets, ...remainingTweets.current.splice(0, 10)])),配合ListFooterComponent展示加载中的ActivityIndicator或"没有更多推文"的结束态;
  • ListEmptyComponent:数据为空时展示欢迎引导视图;
  • viewabilityConfig:配置waitForInteraction: true、itemVisiblePercentThreshold: 50、minimumViewTime: 1000,用于内容可见性统计类场景;
  • ItemSeparatorComponent:以StyleSheet.hairlineWidth的分隔线实现类 Twitter 的细分隔视觉;
  • estimatedItemSize={150}:将推文卡片估算高度提前告知 FlashList,显著降低长列表的初始渲染成本。

同时示例还演示了expand()、collapse()、close()与snapToIndex()等实例方法的组合使用,是阅读BottomSheetFlashList生产级用法的直接入口。

七、常见问题

  • 抛错 "You need to install FlashList first":说明@shopify/flash-list未安装或 Metro 解析失败,请先执行yarn install @shopify/flash-list并重启 Metro;
  • 多滚动组件/React Navigation 场景下手势错乱:为每个BottomSheetFlashList显式传入focusHook={useFocusEffect},让滚动 ref 的注册与焦点同步;
  • footer 遮挡列表末尾内容:开启enableFooterMarginAdjustment,让列表底部自动让出动画 footer 的高度。

结语

BottomSheetFlashList把 FlashList 的高性能列表渲染能力与 bottom sheet 的交互手势做成了开箱即用的组合:安装@shopify/flash-list后即可直接使用,全部FlashListProps原样继承,并额外获得focusHook、enableFooterMarginAdjustment、scrollEventsHandlersHook等 bottom sheet 专属能力。配合 website/docs/components/bottomsheetflashlist.md 与 example/src/screens/integrations/flashlist/FlashListExample.tsx,你可以在几分钟内为 App 加入一个可拖拽、可吸附、可无限滚动的底部弹层列表。

  • 前端
  • 移动开发
  • UI组件
  • 跨平台

【免费下载链接】react-native-bottom-sheet

A performant interactive bottom sheet with fully configurable options 🚀

项目地址:https://gitcode.com/gh_mirrors/re/react-native-bottom-sheet
点击查看免费下载
上一篇:DeFi 协议模板实战:用 Solidity 实现 Governance 治理代币与 Flash Loan 闪电贷(GitHub agents24 项目)
下一篇:终极摄像头流媒体解决方案:go2rtc让跨协议视频流转码变得如此简单

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询