任何状态更改时的 FlatList ScrollView 错误 - 不变违规:不支持即时更改 onViewableItemsChanged

FlatList ScrollView Error on any State Change - Invariant Violation: Changing onViewableItemsChanged on the fly is not supported

onViewableItemsChanged 在应用程序中发生状态更改时似乎不起作用。这是正确的吗?

如果是这样的话,好像用处不大....

否则,用户将被迫向我们onScroll确定位置或类似的东西...

重现步骤

  1. 请参考snack
  2. Repo 也已上传至 github
  3. 使用 onViewableItemsChanged
  4. 时,任何状态更改都会产生错误
  5. 这个错误是什么意思?

注意:将 onViewableItemsChanged 函数放在渲染方法之外的 const 中也无济于事...

<FlatList
    data={this.state.cardData}
    horizontal={true}
    pagingEnabled={true}
    showsHorizontalScrollIndicator={false}
    onViewableItemsChanged={(info) =>console.log(info)}
    viewabilityConfig={{viewAreaCoveragePercentThreshold: 50}}
    renderItem={({item}) =>
        <View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
            <Text>Dogs and Cats</Text>
        </View>
    }
/>

实际行为

错误

将您的 viewabilityConfig 属性移除为渲染函数和 onViewableItemsChanged 函数之外的常量值

您必须将函数传递给绑定在组件构造函数中的 onViewableItemsChanged,并且必须将 viewabilityConfig 设置为 Flatlist.[=14 之外的常量=]

示例:

class YourComponent extends Component {

    constructor() {
        super()
        this.onViewableItemsChanged.bind(this)
    }

    onViewableItemsChanged({viewableItems, changed}) {
        console.log('viewableItems', viewableItems)
        console.log('changed', changed)
    }

    viewabilityConfig = {viewAreaCoveragePercentThreshold: 50}

    render() {
        return(
          <FlatList
            data={this.state.cardData}
            horizontal={true}
            pagingEnabled={true}
            showsHorizontalScrollIndicator={false}
            onViewableItemsChanged={this.onViewableItemsChanged}
            viewabilityConfig={this.viewabilityConfig}
            renderItem={({item}) =>
                <View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
                    <Text>Dogs and Cats</Text>
                 </View>}
          />
        )
    }
}

将 viewabilityConfig 对象移至构造函数。

constructor() {
    this.viewabilityConfig = {
        viewAreaCoveragePercentThreshold: 50
    };
}

render() {
     return(
        <FlatList
            data={this.state.cardData}
            horizontal={true}
            pagingEnabled={true}
            showsHorizontalScrollIndicator={false}
            onViewableItemsChanged={(info) =>console.log(info)}
            viewabilityConfig={this.viewabilityConfig}
            renderItem={({item}) =>
                <View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
                    <Text>Dogs and Cats</Text>
                </View>
            }
        />
    )
}

有人建议使用 Flatlist 的 extraData 属性 让 Flatlist 注意到,有些东西发生了变化。

但这对我不起作用,以下是对我有用的方法:

使用 key={this.state.orientation}orientation 例如 "portrait" 或 "landscape"...它可以是你想要的任何东西,但它必须改变,如果方向改变. 如果 Flatlist 注意到 key-属性 发生了变化,它会重新渲染。

适用于 react-native 0.56

基于@woodpav 评论。使用功能组件和 Hooks。 将 viewabilityConfigonViewableItemsChanged 分配给 refs 并使用它们。如下所示:

  const onViewRef = React.useRef((viewableItems)=> {
      console.log(viewableItems)
      // Use viewable items in state or as intended
  })
  const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 50 })


<FlatList
      horizontal={true}
      onViewableItemsChanged={onViewRef.current}
      data={Object.keys(cards)}
      keyExtractor={(_, index) => index.toString()}
      viewabilityConfig={viewConfigRef.current}
      renderItem={({ item, index }) => { ... }}
/>

这对我有用,有什么方法可以将附加参数传递给 onViewRef 吗?就像下面的代码一样,我如何将类型参数传递给 onViewRef。 代码:

        function getScrollItems(items, isPendingList, type) {
    return (
        <FlatList
            data={items}
            style={{width: wp("100%"), paddingLeft: wp("4%"), paddingRight: wp("10%")}}
            horizontal={true}
            keyExtractor={(item, index) => index.toString()}
            showsHorizontalScrollIndicator={false}
            renderItem={({item, index}) => renderScrollItem(item, index, isPendingList, type)}
            viewabilityConfig={viewConfigRef.current}
            onViewableItemsChanged={onViewRef.current}
        />
    )
}

错误“不支持即时更改 onViewableItemsChanged”,因为当您更新状态时,您正在创建一个新的 onViewableItemsChanged 函数参考,因此您正在即时更改它。

虽然 可以解决 useRef 的问题,但在这种情况下它不是正确的钩子。您应该使用 useCallback 到 return 记忆回调和 useState 来获取当前状态,而无需创建对该函数的新引用。

下面是一个保存所有已查看项目索引的示例:

const MyComp = () => {
  const [cardData] = useState(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']);
  const [viewedItems, setViewedItems] = useState([]);

  const handleVieweableItemsChanged = useCallback(({ changed }) => {
    setViewedItems(oldViewedItems => {
      // We can have access to the current state without adding it
      //  to the useCallback dependencies

      let newViewedItems = null;

      changed.forEach(({ index, isViewable }) => {
        if (index != null && isViewable && !oldViewedItems.includes(index)) {
          
           if (newViewedItems == null) {
             newViewedItems = [...oldViewedItems];
           }
           newViewedItems.push(index);
        }
      });

      // If the items didn't change, we return the old items so
      //  an unnecessary re-render is avoided.
      return newViewedItems == null ? oldViewedItems : newViewedItems;
    });

    // Since it has no dependencies, this function is created only once
  }, []);

  function renderItem({ index, item }) {
    const viewed = '' + viewedItems.includes(index);
    return (
      <View>
        <Text>Data: {item}, Viewed: {viewed}</Text>
      </View>
    );
  }

  return (
    <FlatList
      data={cardData}
      onViewableItemsChanged={handleVieweableItemsChanged}
      viewabilityConfig={this.viewabilityConfig}
      renderItem={renderItem}
    />
  );
}

您可以看到它在 Snack 上运行。

const handleItemChange = useCallback( ({viewableItems}) => {
console.log('here are the chaneges', viewableItems);
if(viewableItems.length>=1)
viewableItems[0].isViewable?
  setChange(viewableItems[0].index):null;

},[])

试试这个对我有用

在平面列表外设置 onViewableItemsChanged 和 viewabilityConfig 解决了我的问题。

const onViewableItemsChanged = useCallback(({ viewableItems }) => {
    if (viewableItems.length >= 1) {
      if (viewableItems[0].isViewable) {
        setItem(items[viewableItems[0].index]);
        setActiveIndex(viewableItems[0].index);
      }
    }
  }, []);



const viewabilityConfig = {
    viewAreaCoveragePercentThreshold: 50,
  };

我正在使用功能组件,我的平面列表如下所示

  <Animated.FlatList
     data={items}
     keyExtractor={item => item.key}
     horizontal
     initialScrollIndex={activeIndex}
     pagingEnabled
     onViewableItemsChanged={onViewableItemsChanged}
     viewabilityConfig={viewabilityConfig}
     ref={flatlistRef}
     onScroll={Animated.event(
       [{ nativeEvent: { contentOffset: { x: scrollX } } }],
       { useNativeDriver: false },
     )}
     contentContainerStyle={{
       paddingBottom: 10,
     }}
     showsHorizontalScrollIndicator={false}
     renderItem={({ item }) => {
       return (
         <View style={{ width, alignItems: 'center' }}>
           <SomeComponent item={item} />
         </View>
       );
     }}
   />