You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免SectionList触发事件时自动重渲染及滚动至顶部问题

问题:点击SectionList Footer后屏幕重渲染并滚动至顶部

点击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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 07:50:28