React-Native更新列表视图数据源

41

我正在使用react-native开发一个iOS应用程序。Game类包含一个ListView组件。我在构造函数中设置状态并包括一个dataSource。目前,我有一个硬编码的数据数组,存储在不同的状态属性(this.state.ds)中。然后,在componentDidMount中,我使用cloneWithRows方法将this.state.ds克隆为视图的dataSource。这在ListViews方面是相当标准的,并且工作得很好。以下是代码:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

 var React = require("react-native");
 var { StyleSheet, Text, View, ListView, TouchableHighlight } = React;

class Games extends React.Component {
constructor(props) {
    super(props);
    var ds = new ListView.DataSource({
        rowHasChanged: (r1, r2) => r1 != r2
    });
    this.state = {
        ds: [
            { AwayTeam: "TeamA", HomeTeam: "TeamB", Selection: "AwayTeam" },
            { AwayTeam: "TeamC", HomeTeam: "TeamD", Selection: "HomeTeam" }
        ],
        dataSource: ds
    };
}

componentDidMount() {
    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(this.state.ds)
    });
}
pressRow(rowData) {
    var newDs = [];
    newDs = this.state.ds;
    newDs[0].Selection = newDs[0] == "AwayTeam" ? "HomeTeam" : "AwayTeam";
    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(newDs)
    });
}

renderRow(rowData) {
    return (
        <TouchableHighlight
            onPress={() => this.pressRow(rowData)}
            underlayColor="#ddd"
        >
            <View style={styles.row}>
                <Text style={{ fontSize: 18 }}>
                    {rowData.AwayTeam} @ {rowData.HomeTeam}{" "}
                </Text>
                <View style={{ flex: 1 }}>
                    <Text style={styles.selectionText}>
                        {rowData[rowData.Selection]}
                    </Text>
                </View>
            </View>
        </TouchableHighlight>
    );
}
render() {
    return (
        <ListView
            dataSource={this.state.dataSource}
            renderRow={this.renderRow.bind(this)}
        />
    );
}
}
var styles = StyleSheet.create({
 row: {
    flex: 1,
    flexDirection: "row",
    padding: 18,
    borderBottomWidth: 1,
    borderColor: "#d7d7d7"
},
selectionText: {
    fontSize: 15,
    paddingTop: 3,
    color: "#b5b5b5",
    textAlign: "right"
}
});


module.exports = Games

我遇到的问题出现在`pressRow`方法中。当用户按下行时,我希望选择项更改并在设备上呈现更改。通过一些调试,我发现尽管我正在更改`newDs`数组中对象的`Selection`属性,但是`this.state.ds`和`this.state.dataSource._dataBlob.s1`中的对象的相同属性也会发生变化。经过进一步的调试,我发现由于那些其他数组已经改变,ListView的DataSource对象无法识别变化,因为当我设置状态并调用`rowHasChanged`时,它克隆的数组与`this.state.dataSource._dataBlob.s1`数组匹配,因此看起来没有任何变化,不会重新渲染。
有什么想法吗?
3个回答

18

如果有人遇到和我一样的问题,这里提供完整的解决方案。

基本上,ListView组件似乎存在一个bug,你需要重建每个在数据源中发生变化的项,以便ListView重新绘制它们。

首先,创建数据源并将数组的副本保存到状态中。在我的情况下,foods是数组。

constructor(props){
    super(props);
    var ds = new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
    });
    this.state = {
        dataSource: ds.cloneWithRows(foods),
        db: foods,
    };
}

当您想要更改数据源中的内容时,请先复制您保存到状态的数组,使用更改重建项,然后将带有更改的新数组保存回状态(包括数据库和数据源)。

onCollapse(rowID: number) {
    console.log("rowID", rowID);
    var newArray = this.state.db.slice();
    newArray[rowID] = {
        key: newArray[rowID].key,
        details: newArray[rowID].details,
        isCollapsed: newArray[rowID].isCollapsed == false ? true : false,
    };
    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(newArray),
        db: newArray,
    });
}

1
根据MDN文档(在此处:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign),你可以使用 var copy = Object.assign({}, obj); 来实现对象的复制,而不是手动重建整个对象。 - Eric G
3
如果您更喜欢ES6语法:newRows[rowId] = { ...this._sampleData[rowId], favorited: rowData.favorited ? false : true }的意思是将 _sampleDatarowId 对应的数据复制到 newRows[rowId],并将该行的 favorited 值取反。如果原来为 true 则变为 false,如果原来为 false 则变为 true - Eric G
这样使用 this.db 而不是 this.state.db 会更好吗? - iou90

18

试试这个:

pressRow(rowData){

    var newDs = [];
    newDs = this.state.ds.slice();
    newDs[0].Selection = newDs[0] == "AwayTeam" ? "HomeTeam" : "AwayTeam";
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(newDs)
    })

}

这将创建一个数组的副本,可以独立于原始数组 this.state.ds 进行修改。


非常感谢!我还需要执行另一个步骤才能使其正常工作。由于某种原因,我必须“清除”我要更改的对象。所以我只需将newDs [0]设置为一个新对象,并赋予它我要更改的对象的属性即可。当然,我已经更改了Selection属性。 - Brandon M
1
this.state.ds.slice() 更改为 this.state.ds.slice(0) 可以提高性能。 - TheNickyYo
@TheNickyYo - 为什么这样呢?文档说未定义会被视为0,不是吗? - Martin Capodici
this.state.ds.slice() 不是一个函数。 - TomSawyer

3

React足够聪明,可以检测到dataSource中的更改以及是否需要重新渲染列表。如果您想要更新listView,请创建新对象而不是更新现有对象的属性。 代码应该像这样:

let newArray = this._rows.slice();
newArray[rowID] = {
  ...this._rows[rowID],
  Selection: !this._rows[rowID].Selection,
};
this._rows = newArray;

let newDataSource = this.ds.cloneWithRows(newArray);
this.setState({
  dataSource: newDataSource
});

您可以阅读更多关于类似的在Github上的问题的信息


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接