FlatList ref scrollToIndex 不是函数

Posted

技术标签:

【中文标题】FlatList ref scrollToIndex 不是函数【英文标题】:FlatList ref scrollToIndex is not a function 【发布时间】:2020-04-06 14:32:05 【问题描述】:

我在 react native 中面临一个似乎长期存在的问题。 我正在使用带有 RN 版本 0.59 的 Expo SDK35。由于代码库很大,我还没有更新到 Expo SDK36 / RN 0.60,但如果这可以解决我的问题,我可以更新。

我有一个 Animated.View 组件,它有一个 FlatList 子组件,我无法使用 FlatList 参考中应该可用的静态方法(尤其是 scrollToIndex())。请参阅下一个示例代码:

class Example extends React.Component
    constructor(props)
        super(props);
        this.myRef = null;
    

    componentDidUpdate = () => 
        /*
            somewhere in code outside this class, a re-render triggers
            and passes new props to this class.
            I do have props change detection, and some more other code,
            but I have removed it in order to minimize the code example here
        */

        // This call throws:
        // TypeError: undefined is not a function (near '...this._scrollRef.scrollTo...')
        this.myRef.scrollToIndex(
            animated: true,
            index: 1,
            viewOffset: 0,
            viewPosition: 0.5
        );

        // Other suggested solution from SO
        // This also throws:
        // TypeError: _this.myRef.getNode is not a function. (In '_this.myRef.getNode()', '_this.myRef.getNode' is undefined)
        this.myRef.getNode().scrollToIndex(
            animated: true,
            index: 1,
            viewOffset: 0,
            viewPosition: 0.5
        );
    
    render = () => <Animated.View style= /* ... some animated props */ >
        <FlatList ref=(flatListRef) =>  this.myRef = flatListRef; 
            // more FlatList related props
        />
    </Animated.View>

我已尝试改用Animated.FlatList,但仍会引发与上述代码示例相同的错误。 我还尝试在收到的flatListRef 参数上使用 react native 的 findNodeHandle() 实用函数,但它返回 null

我发现过去在 Stack Overflow 上多次发布过相同的问题,大多数都没有答案,或者对我不起作用。这些帖子也有点旧(一年左右),这就是为什么我再次发布同一问题的原因。

有人设法找到解决此问题的解决方案/解决方法吗?

编辑:可能的解决方法

当我在玩代码时,我尝试使用 ScrollView 组件而不是 FlatList - scrollTo 方法有效! 更改仅在 FlatList - ScrollView 特定道具上(因此,对于 ScrollView,它将是子级而不是 data=[...]renderItem=()=&gt; ... 等),以及 componentDidMount 中的 scrollToIndex 方法被 scrollTo 替换. 带有 ScrollView 的类的 render 方法现在看起来像这样:

    render = () => <Animated.View style= /* ... some animated props */ >
        <ScrollView ref=(flatListRef) =>  this.myRef = flatListRef; >
            /*
                this.renderItem is almost the same as the
                renderItem method used on the FlatList
             */
              this.state.dataArray.map(this.renderItem) 
        </ScrollView>
    </Animated.View>

请注意,ScrollView 没有 scrollToIndex() 方法,因此您必须手动跟踪子位置,并且可能需要实现自己的 scrollToIndex 方法。

我不会将此作为我问题的答案,因为根本问题仍然存在。但作为一种解决方法,也许你可以接受它并收工......

【问题讨论】:

【参考方案1】:

TL;DR;


this.myRef = React.createRef();
this.myRef.current.doSomething(); // note the use of 'current'

长版:

虽然我尝试的想法是正确的,但我原来帖子中的错误似乎很愚蠢。在我的辩护中,文档并不清楚(可能......)。总之……

React.createRef 返回一个带有几个字段的对象,所有这些字段对开发人员都无用(由 React 在后面使用)- 除了一个:current。 此道具保存对 ref 附加到的基础组件的当前引用。主 ref 对象不能用于我在上面的原始问题中想要达到的目的。

相反,这是我应该正确使用 ref 的方式:

this.myRef.current.scrollToIndex(...)

等一下,不要崩溃

如果组件尚未安装、稍后已卸载或由于某种原因无法附加引用,则主 myRef 对象和 current 字段将是 null。您可能知道(或稍后发现),null.something 会抛出错误。所以,为了避免它:

if ((this.myRef !== null) && (this.myRef.current !== null))
    this.myRef.current.scrollToIndex(...);

额外保险

如果您尝试在 ref 上的字段上调用未定义的值作为函数,您的代码将崩溃。如果您错误地在多个组件上重复使用相同的 ref,或者您附加它的组件没有该方法(即View 没有scrollTo 方法),就会发生这种情况。要解决此问题,您有两种解决方案:

// I find this to be the most elegant solution
if ((this.myRef !== null) && (this.myRef.current !== null)) 
    if (typeof this.myRef.current.scrollToIndex === "function") 
        this.myRef.current.scrollToIndex(...);
    

if ((this.myRef !== null) && (this.myRef.current !== null)) 
    if (typeof this.myRef.current.scrollToIndex === "function") 
        try 
            this.myRef.current.scrollToIndex(...);
         catch (error) 
            console.warn("Something went wrong", error);
        
    

我希望这对学习在 React 中使用 refs 的其他人有用。干杯:)

【讨论】:

谢谢伙计。我正面临着这个,失去了三个宝贵的小时。您的解决方案修复了它。再次感谢...@克里斯蒂安【参考方案2】:

使用 Animated.ScrollView

为您的 FlatList 创建一个引用(旧方法仅适用):

&lt;ScrollView ref= (ref) =&gt; (this.MyRef=ref) /&gt;

使用this.myRef.getNode().scrollToIndex访问scrollToIndex

Animated.FlatList 目前无法正常工作...

使用平面列表

通过以下方式为您的 FlatList 创建一个引用:

&lt;FlatList ref= this.flatListRef /&gt;

  constructor(props) 
    super(props);

    this.flatListRef = React.createRef();
  
使用this.flatListRef.current.scrollToIndex访问scrollToIndex

还要确保将代码包装在 if 语句中,例如:

 if (this.myRef.getNode())  this.flatListRef.getNode().scrollToIndex(); 

【讨论】:

不幸的是,它没有用。我尝试了FlatListAnimated.FlatList 两种方式,它总是抛出this.myRef.scrollToIndex is not a functionthis.myRef.getNode is not a function。 :( 请按照Animated.FlatList 中的步骤...并尝试在componentDidMount 方法中使用console.log(this.flatListRef.getNode()) 我确实按照您描述的步骤进行了操作,并且我还尝试了 Animated.FlatList 和 FlatList、getNode() 和直接调用参考的混合。使用 getNode() 它会在 if 语句中抛出。没有 getNode() 它会抛出引用,就像我最初描述的那样。好消息是 - 我可以改用ScrollView。在 ScrollView 上放置一个 ref 并尝试使用与 FlatList 相同的代码(除了 renderItem、data 和其他与 FlatList 相关的道具),它可以正常工作。我不知道为什么,对我来说没有意义,但是嘿......这可能是一个解决方案...... 我之前尝试过Animated.FlatList,但它没有用......错误......问题是 -> 用 ScrollView 替换你的 FlatList 会导致性能下降(FlatList 更多优化...) 我知道性能下降,但是,我画的孩子很少(确切地说是 4 个),而且我只在需要时以编程方式滚动它们,希望不会对性能产生太大影响。但是,仍然感谢您尝试帮助我:)【参考方案3】:

不知道这是否对您有帮助...它滚动到列表中的特定项目:

/*Example to Scroll to a specific position in scrollview*/
import React,  Component  from 'react';
//import react in our project

import 
  View,
  ScrollView,
  StyleSheet,
  Text,
  TouchableOpacity,
  Image,
  TextInput,
 from 'react-native';
//import all the components we needed

export default class App extends Component 
  constructor() 
    super();
    //Array of Item to add in Scrollview
    this.items = [
      'zero',
      'one',
      'two',
      'three',
      'four',
      'five',
      'six',
      'seven',
      'eight',
      'nine',
      'ten ',
      'eleven',
      'twelve',
      'thirteen',
      'fourteen',
      'fifteen',
      'sixteen',
      'seventeen',
      'eighteen',
      'nineteen',
      'twenty ',
      'twenty-one',
      'twenty-two',
      'twenty-three',
      'twenty-four',
      'twenty-five',
      'twenty-six',
      'twenty-seven',
      'twenty-eight',
      'twenty-nine',
      'thirty',
      'thirty-one',
      'thirty-two',
      'thirty-three',
      'thirty-four',
      'thirty-five',
      'thirty-six',
      'thirty-seven',
      'thirty-eight',
      'thirty-nine',
      'forty',
    ];
    //Blank array to store the location of each item
    this.arr = [];
    this.state =  dynamicIndex: 0 ;
  
  downButtonHandler = () => 
    if (this.arr.length >= this.state.dynamicIndex) 
      // To Scroll to the index 5 element
      this.scrollview_ref.scrollTo(
        x: 0,
        y: this.arr[this.state.dynamicIndex],
        animated: true,
      );
     else 
      alert('Out of Max Index');
    
  ;
  render() 
    return (
      <View style=styles.container>
        <View
          style=
            flexDirection: 'row',
            backgroundColor: '#1e73be',
            padding: 5,
          >
          <TextInput
            value=String(this.state.dynamicIndex)
            numericvalue
            keyboardType='numeric'
            onChangeText=dynamicIndex => this.setState( dynamicIndex )
            placeholder='Enter the index to scroll'
            style= flex: 1, backgroundColor: 'white', padding: 10 
          />
          <TouchableOpacity
            activeOpacity=0.5
            onPress=this.downButtonHandler
            style= padding: 15, backgroundColor: '#f4801e' >
            <Text style= color: '#fff' >Go to Index</Text>
          </TouchableOpacity>
        </View>
        <ScrollView
          ref=ref => 
            this.scrollview_ref = ref;
          >
          /*Loop of JS which is like foreach loop*/
          this.items.map((item, key) => (
            //key is the index of the array
            //item is the single item of the array
            <View
              key=key
              style=styles.item
              onLayout=event => 
                const layout = event.nativeEvent.layout;
                this.arr[key] = layout.y;
                console.log('height:', layout.height);
                console.log('width:', layout.width);
                console.log('x:', layout.x);
                console.log('y:', layout.y);
              >
              <Text style=styles.text>
                key. item
              </Text>
              <View style=styles.separator />
            </View>
          ))
        </ScrollView>
      </View>
    );
  


const styles = StyleSheet.create(
  container: 
    flex: 1,
    paddingTop: 30,
  ,
  separator: 
    height: 1,
    backgroundColor: '#707080',
    width: '100%',
  ,

  text: 
    fontSize: 16,
    color: '#606070',
    padding: 10,
  ,
);

如果我完全错了,请告诉我...

【讨论】:

谢谢,不过,问题不在于如何 滚动到某个索引;问题是从这些组件的 ref 属性返回的 ScrollView 或 FlatList 引用是 无效引用,或者根本不是引用(返回 null)。因此,这使得调用任何静态 scrollTo*...* 方法成为不可能,因为没有东西可以进行调用。【参考方案4】:

因为 ScrollView 没有 scrollToOffset 函数,它只有 scrollTo 函数。

所以让 scrollTo 和 ScrollView 一起使用或 scrollToOffset 和 FlatList 一起使用,它可以正常工作。

【讨论】:

这不是问题,我最初使用的是具有 scrollToIndex 方法的 FlatList。我现在已经发布了我的问题的正确解决方案,请查看我上面的答案:)【参考方案5】:

如果您正在使用“KeyboardAwareFlatList”,则效果很好: https://github.com/APSL/react-native-keyboard-aware-scroll-view/issues/372

简而言之,使用 useRef 并使用KeyboardAwareFlatListinnerRef 属性而不是ref 属性。

【讨论】:

以上是关于FlatList ref scrollToIndex 不是函数的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ref 在 React Native Video 中编辑视频播放器道具

react-native FlatList 滚动到底部的聊天应用程序

如何使用有限的数组在React Native中使平面列表无限滚动

FlatList里面的FlatList滚动问题

React Native FlatList 嵌套在具有相同方向的 FlatList 中

FlatList 动态高度调整