您试图在一个本应是不可变且已被冻结的对象上设置密钥

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 【问题描述】:

在以下示例中:

MapViewListView 的元素显示为注解 单击 ListView 元素应将其绘制成蓝色颜色。 如果MapViewListView 有效地使用状态对象,则会得到奖励

修改ListViewDataSource似乎会在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 试图从列表中删除对象但无法在不可变值上使用变异成员:是一个“让”常量

试图确定文件是不是已被 uuencoded

从不可变对象设置副本是不是可以避免线程损坏? [关闭]

C2x:6.9.2 外部对象定义:为啥“不应是不完整的类型”放在语义而不是约束中?

DDD 中的值对象 - 为啥是不可变的?

子弹物理引擎,如何冻结物体?