如何避免SectionList触发事件时自动重渲染及滚动至顶部问题
点击SectionList的footerComponent时,整个屏幕会重渲染并自动滚动至位置0。尝试过用scrollTo()临时定位,但问题依旧;Modal内部的状态组件会加剧重渲染,memo化Modal又担心过早优化,寻求合理解决方案。
渲染器返回函数
return ( <SafeAreaView style={styles.container}> <ImageBackground source={require("../assets/stars.gif")} style={{ width: Dim.width, height: Dim.height, }} resizeMode="repeat" > {files ? <List /> : null} <CustomModal visible={visible} onPressIn={() => startRecording()} onPressOut={() => stopRecording()} onExitPress={() => setVisible(false)} onConfirm={() => sendToStorage(ID, len)} disabled={!strgUri} onDismiss={() => setRecording(null)} modalButton={[ styles.modal, pressed ? { backgroundColor: Colors.clay } : { backgroundColor: Colors.aegean }, ]} progress={progress} loadingVisible={sending} /> </ImageBackground> </SafeAreaView> );
Animated SectionList组件
const NewSectionList = Animated.createAnimatedComponent(SectionList); const List = React.forwardRef((ref, props) => { const respondRef = useRef(); // 错误:useRef().current会每次渲染创建新ref,无法持久保存SectionList实例 // const scrollRef = useRef().current; const scrollRef = useRef(); // 用useRef存height,避免状态更新触发重渲染 const heightRef = useRef(0); function scrollToSection() { if (respondRef.current) { respondRef.current.measure((x, y, width, height, px, py) => { console.log("height: ", height); console.log("y: ", y); // 用ref存值,不触发状态更新 heightRef.current = py; }); console.log("scrolling to: ", heightRef.current); if (heightRef.current != 0) { setTimeout(() => { console.log("wait height: ", heightRef.current); console.log("dim height: ", Dim.height); console.log("1 height: ", Dim.height - heightRef.current + Dim.height); scrollRef.current?.scrollTo({ x: 0, y: Dim.height - heightRef.current + Dim.height, animated: true, }); }, 1000); } } } // 修复useEffect依赖:只在必要时触发,避免每次渲染都执行 useEffect(() => { scrollToSection(); }, []); // 可根据需求添加依赖,比如respondRef变化时 const yVal = fadeAnim.interpolate({ inputRange: [0, 1], outputRange: [900, 0], }); const animStyle = { transform: [ { translateY: yVal, }, ], }; return( <Animated.View style={[animStyle]}> <NewSectionList ref={scrollRef} stickySectionHeadersEnabled={false} sections={files} keyExtractor={(item) => item.id} style={{ marginLeft: 10, marginBottom: 110, }} renderItem={({ item }) => ( <View style={styles.cardContainer}> <View style={{ alignSelf: "flex-start", position: "absolute", marginTop: 5, }} > <Text p4 dusk> {new Date(item.timestamp).toDateString()} </Text> </View> <TouchableOpacity onPress={() => visitProfile(item.name, item.email)} > <Text p2 aegean> {item.name} </Text> </TouchableOpacity> <View styles={styles.cardTap}> <TouchableNativeFeedback onPress={() => { playFile(item.url, item.id); }} > <AntDesign name="stepforward" size={40} color={Colors.led} /> </TouchableNativeFeedback> </View> </View> )} renderSectionHeader={({ section: { information, origin, userOrigin, emailOrigin, origID, originDate, }, }) => ( <View style={styles.headerContainer}> <View style={{ alignSelf: "flex-start", position: "absolute", marginTop: 5, }} > <Text p3 led> {new Date(originDate).toDateString()} </Text> </View> <View style={styles.holder}> <TouchableOpacity onPress={() => visitProfile(userOrigin, emailOrigin)} > <Text h4 night> {userOrigin} </Text> </TouchableOpacity> </View> <View style={styles.outerInfo}> <Text p3 dusk style={styles.textInfo}> {information ? information : ""} </Text> </View> <View styles={styles.cardTap}> <TouchableNativeFeedback onPress={() => { playFile(origin, origID); }} > <AntDesign name="stepforward" size={40} color={Colors.night} /> </TouchableNativeFeedback> </View> </View> )} renderSectionFooter={({ section: { docId, dataLen } }) => ( <TouchableOpacity ref={respondRef} style={styles.notPressed} onPress={() => { setVisible(true); scrollToSection(); setID(docId); setLen(dataLen); }} > <Text p2 white> add to convo </Text> </TouchableOpacity> )} refreshControl={<RefreshControl onRefresh={() => getData()} />} /> </Animated.View> ); }); // 用memo包裹List组件,避免不必要重渲染 const MemoizedList = React.memo(List);
解决方案步骤
1. 修复scrollRef的初始化错误
原来的const scrollRef = useRef().current;会每次渲染创建新的ref实例,无法持久保存SectionList的引用。改为const scrollRef = useRef();,后续通过scrollRef.current访问组件实例。
2. 用useRef替代useState存储滚动计算值
scrollToSection中原本用setHeight(py)更新状态,会触发组件重渲染。改用useRef存储height值,避免不必要的状态更新和重渲染。
3. 修正useEffect的依赖数组
原useEffect没有依赖,每次组件渲染都会执行scrollToSection,导致重复滚动和重渲染。根据需求添加合适的依赖(比如仅在组件挂载时执行就用[],或在特定状态变化时添加对应依赖)。
4. 优化Modal的props传递
用useCallback包裹传递给CustomModal的回调函数,确保每次渲染时回调引用一致,避免因props变化导致Modal不必要重渲染:
// 在父组件中用useCallback包裹回调 const handleStartRecording = useCallback(() => startRecording(), []); const handleStopRecording = useCallback(() => stopRecording(), []); const handleExitPress = useCallback(() => setVisible(false), []); const handleConfirm = useCallback(() => sendToStorage(ID, len), [ID, len]); const handleDismiss = useCallback(() => setRecording(null), []);
然后将这些回调传递给CustomModal。如果CustomModal是自定义组件,再用React.memo包裹,此时因props引用稳定,不会出现过早优化的问题。
5. 给List组件添加memo化
用React.memo包裹List组件,确保只有当files等核心props变化时才重渲染,避免父组件状态更新时List不必要的重渲染。
6. 避免不必要的滚动触发
点击footer时的scrollToSection可以调整时机,比如在Modal打开后再执行,或者确保滚动计算逻辑仅在需要时触发,避免重复调用。
内容的提问来源于stack exchange,提问作者Nickoder

