2016-07-08 14 views
6

Im folgenden Beispiel eingefroren:Sie haben versucht, den Schlüssel auf ein Objekt einzustellen, die sein unveränderlich gemeint ist, und hat

  • MapView zeigt Elemente eines ListView als Anmerkungen
  • Klick auf ein ListView Element sollte dazu führen, dass es in blau Farbe gemalt wird. wenn
  • Bonus der MapView und ListView effizient den Objektstatus verwenden

Ändern der DataSource von ListView scheint den Konflikt zu verursachen, wenn das active Attribut geändert wird:

Sie haben versucht, den Schlüssel "zu setzen aktiv 'mit dem Wert' false 'auf einem Objekt , das unveränderlich sein soll und eingefroren wurde.

enter image description here

Was ist der richtige Weg, um den Zustand der Einstellung?

RNPlay Example

'use strict'; 

import React, {Component} from 'react'; 
import {AppRegistry,View,ListView,MapView,Text,TouchableOpacity} from 'react-native'; 

var annotations = [ 
     { 
      title: 'A',active: false,latitude: 45,longitude: 26,latitudeDelta: 0.015,longitudeDelta: 0.015, 
     },{ 
      title: 'B',active: false,latitude: 49,longitude: 14,latitudeDelta: 0.015,longitudeDelta: 0.015, 
     },{ 
      title: 'C',active: false,latitude: 26,longitude: 25,latitudeDelta: 0.015,longitudeDelta: 0.015, 
     } 
     ] 

class SampleApp extends Component { 

    constructor(props) { 
    super(props); 
    var ds = new ListView.DataSource({ 
     rowHasChanged: (row1, row2) => row1 !== row2, 
    }); 
    this.state = { 
     region: annotations[0], 
     annotations: annotations, 
     dataSource: ds.cloneWithRows(annotations) 
    }; 
    } 

    handleClick(field) { 
    if (this.previousField) { 
     this.previousField.active = false; 
    } 
    this.previousField = field; 
    field.active = true; 
    this.setState({ 
     region: field, 
    }); 
    } 

    renderField(field) { 
    let color = (field.active == true)?'blue':'yellow'; 

    return (
     <TouchableOpacity onPress={this.handleClick.bind(this,field)}> 
     <Text style={{backgroundColor:color,borderWidth:1}}>{field.title}</Text> 
     </TouchableOpacity> 
    ); 
    } 

    render() { 
    return (
     <View style={{flex:1,flexDirection:'column',alignSelf:'stretch'}}> 
     <MapView 
      style={{flex:0.5,alignSelf:'stretch',borderWidth:1}} 
      region={this.state.region} 
      annotations={this.state.annotations} 
     /> 
     <ListView 
      dataSource={this.state.dataSource} 
      renderRow={(field) => this.renderField(field)} 
     /> 
     </View> 
    ); 
    } 
} 

AppRegistry.registerComponent('SampleApp',() => SampleApp); 

Antwort

5

Das Problem

Wenn Sie field.active = true; oder this.previousField.active = false; gesetzt, sind Sie ein Objekt zu modifizieren (field), die in der Datenquelle Ihres ListView vorhanden ist. Das ListView löst den Fehler aus, da es seine Datenquelle einfriert, wenn Sie es erstellen, cloneWithRows zu verwenden. Dies stellt sicher, dass die Datenquelle nicht außerhalb des normalen React-Komponentenlebenszyklus geändert werden kann (wie setState). Stattdessen werden ListView.DataSource Objekte mit cloneWithRows geändert, die eine Kopie der vorhandenen Datenquelle zurückgibt.

Wenn Sie mit der Redux Bibliothek vertraut sind, ist es sehr ähnlich wie die Philosophie von Minderer Funktionen mit Rück eine Kopie des Staates, anstatt den bestehenden Zustand zu ändern.

die Datasource-Klonen

Um dieses Problem zu lösen, anstatt field Objekte in Ihrer handleClick Funktion mutiert, was Sie wirklich tun mögen, ist ein neues Datenfeld mit Werten (wie active) bereits eingestellt zu schaffen, und Rufen Sie dann setState mit einer neuen Datenquelle für Ihre ListView erstellt mit cloneWithRows. Wenn Sie dies tun, benötigen Sie eigentlich gar nicht den Schlüssel annotations in Ihrem Staat.

-Code ist wahrscheinlich hilfreicher als Worte hier:

handleClick(field) { 

    //iterate over annotations, and update them. 
    //I'm taking 'title' as a unique id property for each annotation, 
    //for the sake of the example. 
    const newAnnotations = annotations.map(a => { 
    //make a copy of the annotation. Otherwise you'll be modifying 
    //an object that's in your listView's datasource, 
    //and therefore frozen. 
    let copyA = {...a}; 
    if (copyA.title === field.title) { 
     copyA.active = true; 
    } else { 
     copyA.active = false; 
    } 
    return copyA; 
    }); 

    this.setState({ 
    region: {...field, active: true}, 
    dataSource: this.state.dataSource.cloneWithRows(newAnnotations), 
    }); 
} 

Ich hoffe, das hilft!Hier ist ein Code-Snippet, das den kompletten Code enthält, den Sie gepostet haben, mit meinen Änderungen. Es funktioniert für mich genau so, wie du es beschrieben hast, auf iOS mit React Native 0.29. Du hast die Frage android-mapview getaggt, also gehe ich davon aus, dass du Android betreibst, aber die Plattform sollte in diesem Fall keinen Unterschied machen.

'use strict'; 
 

 
import React, {Component} from 'react'; 
 
import {AppRegistry,View,ListView,MapView,Text,TouchableOpacity} from 'react-native'; 
 

 
var annotations = [ 
 
     { 
 
      title: 'A',active: false,latitude: 45,longitude: 26,latitudeDelta: 0.015,longitudeDelta: 0.015, 
 
     },{ 
 
      title: 'B',active: false,latitude: 49,longitude: 14,latitudeDelta: 0.015,longitudeDelta: 0.015, 
 
     },{ 
 
      title: 'C',active: false,latitude: 26,longitude: 25,latitudeDelta: 0.015,longitudeDelta: 0.015, 
 
     } 
 
     ] 
 

 
class SampleApp extends Component { 
 

 
    constructor(props) { 
 
    super(props); 
 
    var ds = new ListView.DataSource({ 
 
     rowHasChanged: (row1, row2) => row1 !== row2, 
 
    }); 
 
    this.state = { 
 
     region: annotations[0], 
 
     dataSource: ds.cloneWithRows(annotations) 
 
    }; 
 
    } 
 

 
    handleClick(field) { 
 

 
    //iterate over annotations, and update them. 
 
    //I'm taking 'title' as a unique id property for each annotation, 
 
    //for the sake of the example. 
 
    const newAnnotations = annotations.map(a => { 
 
     //make a copy of the annotation. Otherwise you'll be modifying 
 
     //an object that's in your listView's datasource, 
 
     //and therefore frozen. 
 
     let copyA = {...a}; 
 
     if (copyA.title === field.title) { 
 
     copyA.active = true; 
 
     } else { 
 
     copyA.active = false; 
 
     } 
 
     return copyA; 
 
    }); 
 

 
    this.setState({ 
 
     region: {...field, active: true}, 
 
     dataSource: this.state.dataSource.cloneWithRows(newAnnotations), 
 
    }); 
 
    } 
 

 
    renderField(field) { 
 
    console.log(field); 
 
    let color = (field.active == true)?'blue':'yellow'; 
 

 
    return (
 
     <TouchableOpacity onPress={this.handleClick.bind(this,field)}> 
 
     <Text style={{backgroundColor:color,borderWidth:1}}>{field.title}</Text> 
 
     </TouchableOpacity> 
 
    ); 
 
    } 
 

 
    render() { 
 
    return (
 
     <View style={{flex:1,flexDirection:'column',alignSelf:'stretch'}}> 
 
     <MapView 
 
      style={{flex:0.5,alignSelf:'stretch',borderWidth:1}} 
 
      region={this.state.region} 
 
      annotations={this.state.annotations} 
 
     /> 
 
     <ListView 
 
      dataSource={this.state.dataSource} 
 
      renderRow={(field) => this.renderField(field)} 
 
     /> 
 
     </View> 
 
    ); 
 
    } 
 
} 
 

 
AppRegistry.registerComponent('SampleApp',() => SampleApp);

+1

Es arbeitet mit ios-Plattform und Ihre Erklärungen half wirklich. –

+0

Großartig! Wenn es Ihr Problem löst, würden Sie die Antwort akzeptieren? Vielen Dank! –