Реагирующий список обновляемых данных DataSource

У меня есть приложение iOS, которое я делаю с реакцией. Класс Game содержит компонент ListView. Я устанавливаю состояние в конструкторе и включаю dataSource. У меня есть жестко заданный массив данных прямо сейчас, когда я храню другое свойство состояния (this.state.ds). Затем в componentDidMount я использую метод cloneWithRows для клонирования моего this.state.ds в качестве моего источника данных для представления. Это довольно стандартно, поскольку 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)}>
      </ListView>
    );
  }
}
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. Когда пользователь нажимает на строку, я хочу, чтобы выбор изменился, и чтобы он отображал изменения на устройстве. Через некоторую отладку я заметил, что, хотя я изменяю свойство Selection объекта в массиве newDs, такое же свойство изменяется на объекте в this.state.ds и аналогичным образом изменяет объект в this.state.dataSource._dataBlob.s1. После дальнейшей отладки я обнаружил, что, поскольку эти другие массивы были изменены, объект ListView DataSource не распознает изменение, потому что, когда вызывается состояние и rowHasChanged вызывается, массив, который он клонирует, соответствует массиву this.state.dataSource._dataBlob.s1 и так что это не похоже на изменение и не ререйдер.

Любые идеи?

Ответ 1

Попробуйте следующее:

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.

Ответ 2

В случае, если кто-нибудь столкнется с этой проблемой, как я, вот полное решение.

В принципе, кажется, что есть ошибка с компонентом ListView, и вам нужно перестроить каждый элемент, который изменяется в источнике данных, чтобы ListView перерисовал его.

Вот рабочий пример: https://rnplay.org/apps/GWoFWg

Сначала создайте источник данных и копию массива и сохраните их. В моем случае foods - это массив.

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

Если вы хотите что-то изменить в источнике данных, сделайте копию массива, который вы сохранили в состоянии, перестройте элемент с изменением, а затем сохраните новый массив с изменением обратно в состояние (как db, так и datasource).

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,
    });
}

Ответ 3

реакция достаточно умна, чтобы обнаруживать изменения в источнике данных и если список должен быть повторно отображен. Если вы хотите обновить 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