您试图在一个不可变且已冻结的对象上设置密钥 [英] You attempted to set the key on an object that is meant to be immutable and has been frozen
问题描述
在以下示例中:
-
MapView
将ListView
的元素显示为注释 - 单击在
ListView
元素上应导致将其绘制为蓝色颜色. - 如果
MapView
和ListView
有效使用状态对象
MapView
displays elements of aListView
as annotations- Clicking on a
ListView
element should result in painting it in blue color. - Bonus if the
MapView
andListView
efficiently use the state object
修改active
属性时,修改ListView
的DataSource
似乎引起冲突:
Modifying the DataSource
of ListView
seems to cause the conflict when the active
attribute gets modified:
您尝试将密钥"active"设置为值"false" 本来是不可变的并且已被冻结的对象.
You attempted to set the key 'active' with the value 'false' on an object that is meant to be immutable and has been frozen.
设置状态的正确方法是什么?
What is the right way of setting the state?
'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
进行更改,该对象将返回现有数据源的副本.
When you set field.active = true;
or this.previousField.active = false;
, you're modifying an object (field
) that is present in the datasource of your ListView
. The ListView
throws the error because it freezes its datasource when you create it using cloneWithRows
. This is to ensure that the datasource can't be modified outside of the normal React component lifecycle (like setState
). Instead, ListView.DataSource
objects are designed to be changed with cloneWithRows
, which returns a copy of the existing datasource.
如果您熟悉 Redux 库,则它与具有reducer函数的原理非常相似.返回状态的副本,而不是修改现有状态.
If you're familiar with the Redux library, it's very similar to the philosophy of having reducer functions return a copy of the state, rather than modifying the existing state.
克隆数据源
要解决此问题,而不是在handleClick
函数中更改field
对象,您真正想做的是创建一个已经设置了值的新数据数组(例如active
),然后调用cloneWithRows
创建的ListView
提供新的数据源.如果这样做,实际上甚至根本不需要在状态下使用annotations
键.
To solve this problem, instead of mutating field
objects in your handleClick
function, what you really want to do is create a new data array with values already set (like active
), and then call setState
with a new datasource for your ListView
created with cloneWithRows
. If you do this, you actually don't even need the annotations
key in your state at all.
代码可能比这里的文字更有用:
Code is probably more helpful than words here:
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,但是在这种情况下,平台不应真正起到作用.
I hope this helps! Here's a code snippet containing the complete code you posted, with my modifications. It's working for me just as you described it should on iOS using React Native 0.29. You tagged the question android-mapview, so I'm assuming you're running Android, but the platform shouldn't really make a difference in this case.
'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);
这篇关于您试图在一个不可变且已冻结的对象上设置密钥的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!