React Native Bottom Sheet Modal状态变更时动态高度偶发失效
场景
我使用Gorhom的Bottom Sheet Modal实现了一个带秒表界面的模态框,其中包含一个用于标记计时的TextInput输入框。我为该输入框添加了onFocus和onBlur事件,通过useState(布尔值状态)来隐藏/显示界面中的两个元素,目的是在用户输入时简化界面,显示更多模态框后方的内容。
问题
useState的状态切换完全正常,元素的显示/隐藏速度符合预期,但模态框的高度并非每次都会重新计算渲染。有时即使2/3的界面元素已被隐藏,模态框仍保持原有高度,当前代码仅能60%正常生效。
长按输入框时成功率能提升至90%以上,但用户不会知晓此操作,也不应要求用户这么做。
效果对比
| 正确效果 | 错误效果 |
|---|---|
![]() | ![]() |
相关代码片段
const slimModeHandler = () => { setTimeout(() => { setSlimMode((prevState) => { return true; }); }, 500); };
<View style={styles.textInputWrapper}> <View style={styles.textInputCol}> <BottomSheetTextInput style={styles.textInputField} placeholder="Name this lap" placeholderTextColor={GlobalStyles.colors.font500} clearButtonMode="always" maxLength={20} onChangeText={inputHandlerLapNameState} value={modalMemory.modalMemoryLapNameState} /* keyboardBehavior="interactive" */ /* onPressIn={() => { setSlimMode((prevState) => { return true; }); }} */ onFocus={slimModeHandler} onBlur={() => { setSlimMode((prevState) => { return false; }); }} /> </View> </View>
const dynamicContainerHeight = useMemo(() => { console.log("pre rerendering of dynamicContainerHeight", slimMode); //add 500 ms delay to slimMode /* setTimeout(() => { */ console.log("slimMode during timeOut", slimMode); return { ...styles.modalContainer, paddingBottom: 16, /* height: 120, */ }; /* }, 500); */ }, [slimMode]);
<BottomSheetView style={dynamicContainerHeight} onLayout={handleContentLayout} > </BottomSheetView>
{!slimMode ? ( <TimerModalCreateStopwatch stopwatch={stopwatch} handleLeftButtonPress={handleLeftButtonPress} handleLeftButtonLongPress={handleLeftButtonLongPress} handleLeftButtonLongPressOut={handleLeftButtonLongPressOut} handleRightButtonPress={handleRightButtonPress} modalMemory={modalMemory} /> ) : null}
环境差异
Mac的iOS模拟器上一切100%正常,无法排查问题,推测是旧Mac模拟器性能低于真机;在iPhone上开启远程调试后也能100%正常,推测是调试模式下渲染速度变慢导致时机匹配。因此我怀疑需要在某处添加延迟。
已尝试方案
- 替换
onFocus为onPressIn,成功率提升至80%,但状态切换过早,导致键盘上推输入框的动画体验不佳; - 添加console.log确认
dynamicContainerHeight在onFocus和onBlur时能正确重渲染,结果正常; - 将
dynamicContainerHeight存入状态中; - 用useCallback替代useMemo,并尝试在
onFocus中调用; - 按照包作者建议移除
dynamicContainerHeight以触发额外重渲染,但无效果; - 尝试包作者提供的
keyboardBehavior自定义Hook; - 为
dynamicContainerHeight和useState添加延迟; - 在
dynamicContainerHeight中添加slimMode判断,状态变更时返回静态高度; - 添加KeyboardAvoidingView;
- 尝试包作者提供的collapse方法;
已花费3-4天时间仍未解决,不知下一步该尝试什么。
解决方案建议
1. 手动触发Bottom Sheet高度重计算
利用Gorhom Bottom Sheet提供的ref,在slimMode状态变更后,强制触发高度重新计算:
import { BottomSheetModal, useBottomSheetModal } from '@gorhom/bottom-sheet'; // 组件内获取模态框ref const { bottomSheetModalRef } = useBottomSheetModal(); // 修改状态更新逻辑,触发重计算 const slimModeHandler = () => { setSlimMode(true); // 等待DOM更新后执行,确保内容已变化 requestAnimationFrame(() => { bottomSheetModalRef.current?.recalculateSnapPoints(); }); }; const handleBlur = () => { setSlimMode(false); requestAnimationFrame(() => { bottomSheetModalRef.current?.recalculateSnapPoints(); }); }; // 绑定到输入框 <BottomSheetTextInput // ...其他属性 onFocus={slimModeHandler} onBlur={handleBlur} />
2. 通过key属性强制BottomSheetView重渲染
当slimMode变化时,修改BottomSheetView的key属性,强制组件重新渲染,让Bottom Sheet重新计算内容高度:
<BottomSheetView style={dynamicContainerHeight} onLayout={handleContentLayout} key={`bottom-sheet-content-${slimMode}`} > {/* 模态框内容 */} </BottomSheetView>
3. 优化SnapPoints配置
确保Bottom Sheet的snapPoints设置为自动适应内容,避免固定高度限制:
const snapPoints = useMemo(() => ['auto'], []); // 在BottomSheetModal中使用 <BottomSheetModal ref={bottomSheetModalRef} snapPoints={snapPoints} // ...其他配置 > {/* 内容 */} </BottomSheetModal>
4. 移除不必要的延迟
你当前slimModeHandler中的500ms延迟可能和Bottom Sheet的内部渲染逻辑冲突,尝试移除延迟,改为依赖requestAnimationFrame确保时机正确:
const slimModeHandler = () => { setSlimMode(true); requestAnimationFrame(() => { bottomSheetModalRef.current?.recalculateSnapPoints(); }); };
内容的提问来源于stack exchange,提问作者Boavista_Ludwig



