Рендеринг и конструктор в реагирующем упорядочении console.log

У меня небольшие трудности с пониманием потока программы в этом родном приложении для реагирования.

Я поместил операторы console.log в приложение, чтобы попытаться понять.

Поэтому я инициализирую состояние в конструкторе на lat: 0, long: 0, err: null и markers: []. Затем в конструкторе я вызываю свой метод API, чтобы получить все местоположения и заполнить метод маркеров. Вывод с отпечатков следующий:

//Из метода рендеринга

[]
[]
[]

//Затем возвращается асинхронный метод конструктора и мы устанавливаем маркеры

{markers: [{title: "A", coordinate: {latitude: 0, longitude: 1}, description: "abc"},
{title: "B", coordinate: {latitude: 0, longitude: 1}, description: "abc"}]}

Затем я вызываю метод рендеринга, я понимаю, что это, вероятно, ужасная практика, но это была просто попытка отладки, надеясь, что тогда маркеры будут видны на карте.

Однако после этого метод рендеринга продолжает печатать [], что мне кажется очень странным, поскольку я только что установил его в конструкторе!

Any help would be appreciated.

import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  View
} from 'react-native';
import MapView from 'react-native-maps';

const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    justifyContent: 'flex-end',
    alignItems: 'center',
  },
  map: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
});

const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,\n' +
    'Cmd+D or shake for dev menu',
  android: 'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu',
});

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      latitude: 0,
      longitude: 0,
      error: null,
      markers: []
    };
    navigator.geolocation.getCurrentPosition(
      (position) => {
        this.state = {
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
          error: null,
        };
      },
      (error) => this.setState({ error: error.message }),
        { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 },
      );
    fetch(API_URL)
      .then((response) => response.json())
      .then((responseJson) => {
        var relevantLocations = []
        for (var i = 0; i < responseJson.length; i++) {
          var location = responseJson[i];
          relevantLocations.push({
            title: location.name,
            coordinate: {latitude: location.latitude, longitude: location.longitude},
            description: "test" + i
          });
        }
        console.log("Setting state");
        this.state = {
          markers: relevantLocations
        };
        console.log(this.state);
        this.render();
      })
      .catch((error) => {
        console.error(error);
      });
  }

  onRegionChange = (region) => {
    this.setState({ region });
  }

  onPress = () => {
  }

  render() {
    console.log(this.state.markers);
    return (
      <View style={styles.container}>
        <MapView style={styles.map}
          onRegionChange={this.onRegionChange}
          onPress={this.onPress}
        >
        {this.state.markers.map(marker => {
            return <MapView.Marker
              key={marker}
              coordinate={marker.coordinate}
              title={marker.title}
              description={marker.description}
            />
          })}
        </MapView>
      </View>
    );
  }
}

export default App;

Как получается, что переменные, которые я устанавливаю в конструкторе, перезаписываются?

Спасибо


person SwimmingG    schedule 14.10.2017    source источник


Ответы (1)


Обычно асинхронные вызовы в React помещаются в метод жизненного цикла componentDidMount(), который вызывается сразу после первоначального вызова render(). constructor предназначен для инициализации, т. е. инициализации компонента state, вызова super для любого переданного props, bind методов компонента. Я бы переместил все ваши асинхронные вызовы, включая вызовы navigator и fetchAPI, в componentDidMount и удостоверился, что какой бы state вы ни инициализировали, он не вызовет ошибок в render. Как пишет Тайлер Макгиннис в этом сообщении:

Запросы AJAX должны идти в событии жизненного цикла componentDidMount.

На это есть несколько причин,

Fiber, следующая реализация алгоритма согласования React, будет иметь возможность запускать и останавливать рендеринг по мере необходимости для повышения производительности. Одним из компромиссов этого является то, что componentWillMount, другое событие жизненного цикла, когда может иметь смысл сделать запрос AJAX, будет «недетерминированным». Это означает, что React может начать вызывать componentWillMount в разное время, когда посчитает нужным. Это, очевидно, будет плохой формулой для запросов AJAX.

Вы не можете гарантировать, что запрос AJAX не будет разрешен до монтирования компонента. Если бы это было так, это означало бы, что вы пытаетесь установить состояние на несмонтированном компоненте, что не только не сработает, но и React будет кричать на вас. Выполнение AJAX в componentDidMount гарантирует наличие компонента для обновления.

Вот полностью переработанный пример с использованием componentDidMount и правильной установкой state после разрешения всех асинхронных запросов.

import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  View
} from 'react-native';
import MapView from 'react-native-maps';

const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    justifyContent: 'flex-end',
    alignItems: 'center',
  },
  map: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
});

const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,\n' +
    'Cmd+D or shake for dev menu',
  android: 'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu',
});

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      latitude: 0,
      longitude: 0,
      error: null,
      markers: []
    };
  }

  componentDidMount() {
  
     navigator.geolocation.getCurrentPosition(
      (position) => {
        
        fetch(API_URL)
           .then((response) => response.json())
           .then((responseJson) => {
              var relevantLocations = []
              for (var i = 0; i < responseJson.length; i++) {
                var location = responseJson[i];
                relevantLocations.push({
                  title: location.name,
                  coordinate: {latitude: location.latitude, longitude: 
                    location.longitude},
                  description: "test" + i
                });
              }
             console.log("Setting state");
             this.setState({
                ...this.state
                latitude: position.coords.latitude,
                longitude: position.coords.longitude,
                markers: relevantLocations
             });
           })
           .catch((error) => {
             console.error(error);
           });

       },
      (error) => this.setState({ ...this.state, error: error.message }),
        { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 },
      );

    
  }

  onRegionChange = (region) => {
    this.setState({ region });
  }

  onPress = () => {
  }

  render() {
    console.log(this.state.markers);
    return (
      <View style={styles.container}>
        <MapView style={styles.map}
          onRegionChange={this.onRegionChange}
          onPress={this.onPress}
        >
        {this.state.markers.map(marker => {
            return <MapView.Marker
              key={marker}
              coordinate={marker.coordinate}
              title={marker.title}
              description={marker.description}
            />
          })}
        </MapView>
      </View>
    );
  }
}

export default App;
person Parker Ziegler    schedule 14.10.2017
comment
Также кажется, что вы явно пытаетесь сбросить значение state несколько раз в вашем constructor, что является анти-шаблоном. Вы должны вызвать this.state один раз в своем constructor, чтобы инициализировать state, а затем использовать setState в других методах вашего компонента, чтобы фактически обновить состояние. - person Parker Ziegler; 14.10.2017
comment
Спасибо, я ценю помощь. О том, чтобы попробовать сейчас, и дам вам знать, как это происходит. Спасибо! - person SwimmingG; 14.10.2017
comment
Привет, Паркер, спасибо за совет. Было странно устанавливать состояние несколько раз, хотя это было для разных свойств. Спасибо большое, я тоже попробую. - person SwimmingG; 14.10.2017
comment
Еще одну вещь я заметил. Когда вы вызываете setState, вы должны быть уверены, что скопировали все свойства state. Многие люди используют оператор spread ..., чтобы сделать это следующим образом: this.setState({ ...this.state, markers: response.data }); То, что вы говорите с таким утверждением, — это копирование всех свойств state, кроме свойства markers, которое я хочу изменить, чтобы оно было присвоено response.data. Если вы не скопируете свойства, ваш новый объект state будет только иметь те свойства, которые вы ему передали. - person Parker Ziegler; 14.10.2017
comment
Ах! идеально! Большое спасибо и за этот совет! - person SwimmingG; 14.10.2017
comment
Привет, ребята! Большое спасибо за помощь. Это исправлено! Что-то я застрял на весь день! Действительно очень ценю это. - person SwimmingG; 14.10.2017
comment
Рад это слышать! - person Parker Ziegler; 14.10.2017