Dans l'exemple suivant:
MapView
affiche les éléments d'un ListView
sous la forme d'annotations ListView
devrait avoir pour résultat de le peindre en bleu Couleur.MapView
et ListView
utilisent efficacement l'objet stateLa modification de DataSource
de ListView
semble provoquer le conflit lorsque l'attribut active
est modifié:
Vous avez tenté de définir la clé "active" avec la valeur "false" sur un objet qui est censé être immuable et a été gelé.
Quelle est la bonne façon de définir l'État?
'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);
Le problème
Lorsque vous définissez field.active = true;
ou this.previousField.active = false;
, vous modifiez un objet (field
) qui est présent dans la source de données de votre ListView
. ListView
renvoie l'erreur car il fige sa source de données lorsque vous la créez à l'aide de cloneWithRows
. Ceci permet de garantir que la source de données ne peut pas être modifiée en dehors du cycle de vie normal du composant React (comme setState
). Au lieu de cela, ListView.DataSource
les objets sont conçus pour être modifiés avec cloneWithRows
, qui renvoie une copie de la source de données existante.
Si vous connaissez la bibliothèque Redux , elle est très similaire à la philosophie selon laquelle les fonctions de réduction retournent un copie de l'état, plutôt que de modifier l'état existant.
Clonage de la source de données
Pour résoudre ce problème, au lieu de muter les objets field
dans votre fonction handleClick
, ce que vous voulez vraiment faire, c'est créer un nouveau tableau de données avec des valeurs déjà définies (comme active
) , puis appelez setState
avec une nouvelle source de données pour votre ListView
créée avec cloneWithRows
. Si vous faites cela, vous n'avez même pas du tout besoin de la clé annotations
dans votre état.
Le code est probablement plus utile que les mots ici:
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),
});
}
J'espère que ça aide! Voici un extrait de code contenant le code complet que vous avez publié, avec mes modifications. Cela fonctionne pour moi comme vous l'avez décrit sur iOS en utilisant React Native 0.29. Vous avez marqué la question Android-mapview, donc je suppose que vous utilisez Android, mais la plate-forme ne devrait pas '' t vraiment faire une différence dans ce cas.
'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);