【问题标题】: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);

【问题讨论】:

    标签: listview reactjs react-native state android-mapview


    【解决方案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 平台,您的解释确实有帮助。
    • 太棒了!如果它解决了你的问题,你会接受答案吗?谢谢!
    猜你喜欢
    • 2019-01-20
    • 2021-05-29
    • 2019-12-28
    • 2017-02-26
    • 2018-02-13
    • 1970-01-01
    • 2012-07-15
    • 2016-03-12
    相关资源
    最近更新 更多