您试图在一个本应是不可变且已被冻结的对象上设置密钥
Posted
技术标签:
【中文标题】您试图在一个本应是不可变且已被冻结的对象上设置密钥【英文标题】:You attempted to set the key on an object that is meant to be immutable and has been frozen 【发布时间】:2016-11-11 05:54:37 【问题描述】:在以下示例中:
MapView
将ListView
的元素显示为注解
单击 ListView
元素应将其绘制成蓝色颜色。
如果MapView
和ListView
有效地使用状态对象,则会得到奖励
修改ListView
的DataSource
似乎会在active
属性被修改时引起冲突:
您试图将键 'active' 设置为 本来应该是不可变的并且已被冻结的对象。
设置状态的正确方法是什么?
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);
【问题讨论】:
【参考方案1】:问题
当您设置field.active = true;
或this.previousField.active = false;
时,您正在修改ListView
的数据源中存在的对象(field
)。 ListView
会引发错误,因为当您使用 cloneWithRows
创建它时,它会冻结其数据源。这是为了确保不能在正常的 React 组件生命周期之外修改数据源(如 setState
)。相反,ListView.DataSource
对象被设计为使用cloneWithRows
进行更改,这将返回现有数据源的副本。
如果您熟悉 Redux 库,它与让 reducer 函数返回状态的副本而不是修改现有状态的理念非常相似。
克隆数据源
为了解决这个问题,你真正想要做的是创建一个已经设置值的新数据数组(如active
),然后调用@987654334 @ 为您的 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),
);
我希望这会有所帮助!这是一个包含您发布的完整代码的代码 sn-p 以及我的修改。正如您在使用 React Native 0.29 的 ios 上描述的那样,它对我有用。您标记了问题 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);
【讨论】:
它适用于 ios 平台,您的解释确实有帮助。 太棒了!如果它解决了你的问题,你会接受答案吗?谢谢!以上是关于您试图在一个本应是不可变且已被冻结的对象上设置密钥的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI 试图从列表中删除对象但无法在不可变值上使用变异成员:是一个“让”常量