次の例では:
MapView
は、ListView
の要素をannotationsとして表示しますListView
要素をクリックすると、blue 色。MapView
およびListView
が状態オブジェクトを効率的に使用する場合のボーナスDataSource
のListView
を変更すると、active
属性が変更されたときに競合が発生するようです。
不変であることが意図され、凍結されているオブジェクトに対して、キー「アクティブ」を値「偽」に設定しようとしました。
状態を設定する正しい方法は何ですか?
'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);
問題
field.active = true;
またはthis.previousField.active = false;
を設定すると、field
のデータソースに存在するオブジェクト(ListView
)を変更していることになります。 ListView
を使用して作成するとデータソースがフリーズするため、cloneWithRows
はエラーをスローします。これは、データソースが通常のReactコンポーネントライフサイクル(setState
など)以外で変更できないようにするためです。代わりに、ListView.DataSource
オブジェクトは既存のデータソースのcopyを返すcloneWithRows
で変更されました。
Redux ライブラリに精通している場合、reducer関数が状態を変更するのではなく、状態のcopyを返すという哲学に非常に似ています既存の状態。
データソースの複製
この問題を解決するには、field
関数のhandleClick
オブジェクトを変更する代わりに、本当に設定したい値(active
など)で新しいデータ配列を作成します、[setState
で作成されたListView
の新しいデータソースで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),
});
}
これがお役に立てば幸いです!ここに、あなたが投稿した完全なコードを含むコードスニペットと、私の修正を示します。 iOSでReact Native 0.29を使用して説明したとおりに機能します。質問にAndroid-mapviewをタグ付けしたので、Androidを実行していると仮定しますが、プラットフォームは 'この場合、本当に違いが生じます。
'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);