web-dev-qa-db-ja.com

不変であることが意図され、凍結されているオブジェクトにキーを設定しようとしました

次の例では:

  • MapViewは、ListViewの要素をannotationsとして表示します
  • ListView要素をクリックすると、blue 色。
  • MapViewおよびListViewが状態オブジェクトを効率的に使用する場合のボーナス

DataSourceListViewを変更すると、active属性が変更されたときに競合が発生するようです。

不変であることが意図され、凍結されているオブジェクトに対して、キー「アクティブ」を値「偽」に設定しようとしました。

enter image description here

状態を設定する正しい方法は何ですか?

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);
15
Peter G.

問題

field.active = true;またはthis.previousField.active = false;を設定すると、fieldのデータソースに存在するオブジェクト(ListView)を変更していることになります。 ListViewを使用して作成するとデータソースがフリーズするため、cloneWithRowsはエラーをスローします。これは、データソースが通常のReactコンポーネントライフサイクル(setStateなど)以外で変更できないようにするためです。代わりに、ListView.DataSourceオブジェクトは既存のデータソースのcopyを返すcloneWithRowsで変更されました。

Redux ライブラリに精通している場合、reducer関数が状態を変更するのではなく、状態のcopyを返すという哲学に非常に似ています既存の状態。

データソースの複製

この問題を解決するには、field関数のhandleClickオブジェクトを変更する代わりに、本当に設定したい値(activeなど)で新しいデータ配列を作成します、[setStateで作成された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),
  });
}

これがお役に立てば幸いです!ここに、あなたが投稿した完全なコードを含むコードスニペットと、私の修正を示します。 iOSでReact Native 0.29を使用して説明したとおりに機能します。質問に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);
22
Michael Helvey