reactjs ScrollView分页导致所有子级重新呈现

b5lpy0ml  于 2022-12-29  发布在  React
关注(0)|答案(1)|浏览(120)
    • 问题:**

在ReactNative中使用ScrollView进行水平分页时,它会重新呈现所有子组件,但我希望保留子组件的某些局部输入字段和局部变量的状态值。
在下面的代码中,如果我正在更新NotesSection中的TextInput,但希望扫回BatchSection以查看一些元数据,则代码将重新呈现NotesSection并重置保存文本值的本地状态。

    • 诊断:**

我对React和React Native还很陌生,但我最好的猜测是,这是由于父状态变量"horizontalPos"导致的,该变量使用整数来反映焦点所在的页面。这只是在ProductHeader组件中使用,以突出显示彩色bottomBorder,向用户显示屏幕顶部的一种小"菜单"。"horizontalPos"状态可以通过两种方式更新:
1.第一种是在ProductHeader中单击所需标题 (TouchableOpacity) 时,这会触发状态更改并使用useRef自动移动ScrollView。
1.第二个选项是当用户在ScrollView上滑动时,使用OnScroll运行函数"handleHorizontalScroll",该函数反过来使用contentOffset.x中的简单数学设置"horizontalPos"状态。

    • 问题/解决方案:**

如果"horizontalPos"状态是INSIDE ProductHeader,我怀疑这会解决问题,但我无法思考如何做到这一点,因为我不相信有可能基于父组件中的更改将函数传递给子组件。
我依赖于在主ScrollView上注册OnScroll,其余组件同样必须位于主ScrollView内部,但我不希望每次"horizontalPos"状态更新时都重新呈现它们。

    • 代码:**
const ProductScreen = (props) => {
    const [horizontalPos, setHorizontalPos] = useState(0)
  
    const scrollRef = useRef()

    const toggleHorizontal = (page) => {
        setHorizontalPos(page)
        scrollRef.current.scrollTo({x:page*width, y:0, animated:false})
    }

    const handleHorizontalScroll = (v) => {
        const pagination = Math.round(v.nativeEvent.contentOffset.x / width)
        if (pagination != horizontalPos){
          setHorizontalPos(pagination)
        }
    }

    const ProductHeader = () => {
        return(
            <View style={styles.scrollHeaderContainer}>
                <TouchableOpacity style={[styles.scrollHeader, horizontalPos == 0 ? {borderColor: AppGreenDark,} : null]} onPress={() => toggleHorizontal(0)}>
                    <Text style={styles.scrollHeaderText}>Meta Data</Text>
                </TouchableOpacity>
                
                <TouchableOpacity style={[styles.scrollHeader, horizontalPos == 1 ? {borderColor: AppGreenDark,} : null]} onPress={() => toggleHorizontal(1)}>
                    <Text style={styles.scrollHeaderText}>{"Notes"}</Text>
                </TouchableOpacity>
            </View>
        )
    }

    return (
        <View style={styles.container}>
            <ProductHeader/>

            <ScrollView
                ref={scrollRef}
                decelerationRate={'fast'}
                horizontal={true}
                showsHorizontalScrollIndicator={false}
                snapToInterval={width}
                onScroll={handleHorizontalScroll}
                scrollEventThrottle={16}
                disableIntervalMomentum={true}
                style={{flex: 1}}
            >
                <View style={[styles.horizontalScroll]}>
                    <View style={styles.mainScrollView}>
                        <BatchSection/>
                    </View>

                    <ScrollView style={styles.notesScrollView}>
                        <NotesSection/>
                    </ScrollView>
                </View>

            </ScrollView>
        </View>
    )
}
t3irkdon

t3irkdon1#

如您所述,更新ProductScreen内部的horizontalPos状态将导致整个屏幕重新呈现,这不是预期行为。
为了避免这种情况,让我们按如下方式重构代码:

function debounce(func, timeout = 500){
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => { func.apply(this, args); }, timeout);
  };
}

class ProductHeader extends React.Component  {
  state = {horizontalPos:0 }
   toggleHorizontal = (page) => {
      this.setState({horizontalPos:page});
      this.props.onPositionChange(page);
    };

render () {
  const {horizontalPos} = this.state
   return (
      <View style={styles.scrollHeaderContainer}>
        <TouchableOpacity
          style={[
            styles.scrollHeader,
            horizontalPos == 0 ? { borderColor: AppGreenDark } : null,
          ]}
          onPress={() => this.toggleHorizontal(0)}
        >
          <Text style={styles.scrollHeaderText}>Meta Data</Text>
        </TouchableOpacity>

        <TouchableOpacity
          style={[
            styles.scrollHeader,
            horizontalPos == 1 ? { borderColor: AppGreenDark } : null,
          ]}
          onPress={() => this.toggleHorizontal(1)}
        >
          <Text style={styles.scrollHeaderText}>{"Notes"}</Text>
        </TouchableOpacity>
      </View>
    );
  
}

   
  };



const ProductScreen = (props) => {
  const scrollRef = useRef();
  const productHeaderRef = useRef()
  let horizontalPos = 0;

  const handleHorizontalScroll = (v) => {
    const pagination = Math.round(v.nativeEvent.contentOffset.x / width);
    if (pagination != horizontalPos) {
      productHeaderRef.current?.toggleHorizontal(pagination)
    }
  };
  
    const debouncedHorizontalScroll= debounce(handleHorizontalScroll,500)

  const onPositionChange = (page) => {
    horizontalPos = page;
    scrollRef.current.scrollTo({ x: page * width, y: 0, animated: false });
  };

  return (
    <View style={styles.container}>
      <ProductHeader onPositionChange={onPositionChange} ref={productHeaderRef} />

      <ScrollView
        ref={scrollRef}
        decelerationRate={"fast"}
        horizontal={true}
        showsHorizontalScrollIndicator={false}
        snapToInterval={width}
        onScroll={debouncedHorizontalScroll}
        scrollEventThrottle={16}
        disableIntervalMomentum={true}
        style={{ flex: 1 }}
      >
        <View style={[styles.horizontalScroll]}>
          <View style={styles.mainScrollView}>
            <BatchSection />
          </View>

          <ScrollView style={styles.notesScrollView}>
            <NotesSection />
          </ScrollView>
        </View>
      </ScrollView>
    </View>
  );
};

我希望这将阻止整个屏幕重新呈现和维护分页。

相关问题