您试图在一个不可变且已冻结的对象上设置密钥 [英] You attempted to set the key on an object that is meant to be immutable and has been frozen

查看:172
本文介绍了您试图在一个不可变且已冻结的对象上设置密钥的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在以下示例中:

  • MapViewListView的元素显示为注释
  • 单击ListView元素上应导致将其绘制为蓝色颜色.
  • 如果MapViewListView有效使用状态对象
  • MapView displays elements of a ListView as annotations
  • Clicking on a ListView element should result in painting it in blue color.
  • Bonus if the MapView and ListView efficiently use the state object

修改active属性时,修改ListViewDataSource似乎引起冲突:

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?

RNPlay示例

'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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆