您试图在一个本应不可变且已被冻结的对象上设置密钥
You attempted to set the key on an object that is meant to be immutable and has been frozen
在下面的例子中:
MapView
将 ListView
的元素显示为 annotations
- 在
ListView
元素上单击 应该会导致将其绘制为 blue 颜色。
- 如果
MapView
和 ListView
有效地使用状态对象,则奖励
修改 ListView
的 DataSource
似乎会在修改 active
属性时引起冲突:
You attempted to set the key 'active' with the value 'false' on an
object that is meant to be immutable and has been frozen.
设置状态的正确方法是什么?
'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;
时,您正在修改 ListView
的数据源中存在的对象 (field
)。 ListView
抛出错误,因为它在您使用 cloneWithRows
创建它时冻结了它的数据源。这是为了确保无法在正常的 React 组件生命周期之外修改数据源(如 setState
)。相反,ListView.DataSource
对象被设计为使用 cloneWithRows
进行更改,return 是现有数据源的 副本 。
如果您熟悉 Redux 库,它与具有 reducer 函数的原理非常相似 return 状态的 copy,而不是修改现有状态。
克隆数据源
为了解决这个问题,不是在你的 handleClick
函数中改变 field
对象,你真正想要做的是创建一个已经设置值的新数据数组(比如 active
),然后使用 cloneWithRows
创建的 ListView
的新数据源调用 setState
。如果你这样做,你实际上甚至根本不需要你所在州的 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);
使用扩展运算符更新状态或任何不可变变量
例如 my_state 是我的状态变量:
state = {
my_state: {
key1: value1,
}
}
如果要更新此状态,请使用展开运算符
let new_state = {...this.state.state_one};
this.setState({my_state:new_state});
在下面的例子中:
MapView
将ListView
的元素显示为 annotations- 在
ListView
元素上单击 应该会导致将其绘制为 blue 颜色。 - 如果
MapView
和ListView
有效地使用状态对象,则奖励
修改 ListView
的 DataSource
似乎会在修改 active
属性时引起冲突:
You attempted to set the key 'active' with the value 'false' on an object that is meant to be immutable and has been frozen.
设置状态的正确方法是什么?
'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;
时,您正在修改 ListView
的数据源中存在的对象 (field
)。 ListView
抛出错误,因为它在您使用 cloneWithRows
创建它时冻结了它的数据源。这是为了确保无法在正常的 React 组件生命周期之外修改数据源(如 setState
)。相反,ListView.DataSource
对象被设计为使用 cloneWithRows
进行更改,return 是现有数据源的 副本 。
如果您熟悉 Redux 库,它与具有 reducer 函数的原理非常相似 return 状态的 copy,而不是修改现有状态。
克隆数据源
为了解决这个问题,不是在你的 handleClick
函数中改变 field
对象,你真正想要做的是创建一个已经设置值的新数据数组(比如 active
),然后使用 cloneWithRows
创建的 ListView
的新数据源调用 setState
。如果你这样做,你实际上甚至根本不需要你所在州的 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);
使用扩展运算符更新状态或任何不可变变量
例如 my_state 是我的状态变量:
state = {
my_state: {
key1: value1,
}
}
如果要更新此状态,请使用展开运算符
let new_state = {...this.state.state_one};
this.setState({my_state:new_state});