Как создавать вызовы API в REACT и FLUX

Я новичок в реагировании и потоке, и мне трудно найти способы загрузить данные с сервера. Я могу загрузить те же данные из локального файла без проблем.

Итак, у меня есть этот вид контроллера (controller-view.js), который передает начальное состояние в view (view.js)

Контроллер-view.js

var viewBill = React.createClass({
getInitialState: function(){
    return {
        bill: BillStore.getAllBill()
    };
},
render: function(){
    return (
        <div>
            <SubscriptionDetails subscription={this.state.bill.statement} />
        </div>
    );
}
 });
 module.exports = viewBill;

view.js

var subscriptionsList = React.createClass({
propTypes: {
    subscription: React.PropTypes.array.isRequired
},
render: function(){

   return (
        <div >
            <h1>Statement</h1>
            From: {this.props.subscription.period.from} - To {this.props.subscription.period.to} <br />
            Due: {this.props.subscription.due}<br />
            Issued:{this.props.subscription.generated}
        </div>
    );
}
 });
 module.exports = subscriptionsList;

У меня есть файл действий, который загружает данные INITAL для моего приложения. Таким образом, это данные, которые не вызваны как действие пользователя, но вызваны из getInitialState в представлении контроллера

InitialActions.js

var InitialiseActions = {
initApp: function(){
    Dispatcher.dispatch({
        actionType: ActionTypes.INITIALISE,
        initialData: {
            bill: BillApi.getBillLocal() // I switch to getBillServer for date from server
        }
    });
}
};
module.exports = InitialiseActions;

И тогда мой API данных выглядит так:

api.js

var BillApi = {
getBillLocal: function() {
    return billed;
},
getBillServer: function() {
    return $.getJSON('https://theurl.com/stuff.json').then(function(data) {

        return data;
    });
}
};
module.exports = BillApi;

И это магазин store.js

var _bill = [];
var BillStore = assign({}, EventEmitter.prototype, {
addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
},
emitChange: function() {
    this.emit(CHANGE_EVENT);
},
getAllBill: function() {
    return _bill;
}
});

Dispatcher.register(function(action){
switch(action.actionType){
    case ActionTypes.INITIALISE:
        _bill = action.initialData.bill;
        BillStore.emitChange();
        break;
    default:
        // do nothing
}
});

module.exports = BillStore;

Так, как я упоминал ранее, когда я загружаю данные локально с помощью BillApi.getBillLocal(), в действиях все работает нормально. Но когда я перехожу к BillApi.getBillServer(), я получаю следующие ошибки в консоли...

Warning: Failed propType: Required prop `subscription` was not specified in     `subscriptionsList`. Check the render method of `viewBill`.
Uncaught TypeError: Cannot read property 'period' of undefined

Я также добавил console.log(данные) в BillApi.getBillServer(), и я вижу, что данные возвращаются с сервера. Но он отображается ПОСЛЕ. Я получаю предупреждения в консоли, которые, я считаю, могут быть проблемой. Может ли кто-нибудь предложить какие-либо советы или помочь мне исправить это? Извините за такой длинный пост.

UPDATE

Я внесла некоторые изменения в файл api.js(здесь вы можете найти изменения и ошибки DOM plnkr.co/edit/HoXszori3HUAwUOHzPLG), поскольку было высказано предположение, что проблема связана с тем, как я выполняю обещание. Но это все еще похоже на ту же проблему, что и в ошибках DOM.

Ответы

Ответ 1

Это проблема async. Использовать $.getJSON().then() недостаточно. Поскольку он возвращает объект обещания, вы должны обрабатывать обещание при вызове, выполняя что-то вроде api.getBill().then(function(data) { /*do stuff with data*/ });

Я сделал пример CodePen со следующим кодом:

function searchSpotify(query) {
  return $.getJSON('http://ws.spotify.com/search/1/track.json?q=' + query)
  .then(function(data) {
    return data.tracks;
  });  
}

searchSpotify('donald trump')
.then(function(tracks) {
  tracks.forEach(function(track) {
    console.log(track.name);
  });
});

Ответ 2

Похоже, что из вашего кода предполагаемый поток выглядит примерно так:

  • некоторый компонент запускает действие инициализации,
  • инициализировать вызовы действий API
  • который ждет результатов от сервера (я думаю, что здесь происходит разложение вещей: рендеринг компонента начинается до того, как результаты возвращаются сервером),
  • затем передает результат в хранилище,
  • который испускает изменения и
  • запускает повторную визуализацию.

В типичной настройке потока я бы посоветовал структурировать это несколько иначе:

  • какой-то компонент вызывает API (, но не срабатывает для диспетчера еще)
  • API выполняет getJSON и ожидает результатов сервера.
  • только после получения результатов, API запускает действие INITIALIZE с полученными данными
  • отвечает на действия и обновляется с результатами
  • затем испускает изменения
  • который запускает повторную визуализацию

Я не очень хорошо знаком с jquery, promises и цепочкой, но я думаю, что это примерно перевело бы на следующие изменения в вашем коде:

  • Контроллер-просмотр нуждается в прослушивателе изменений в хранилище: добавьте функцию componentDidMount(), которая добавляет прослушиватель событий в изменения хранилища потока.
  • в представлении контроллера, прослушиватель событий запускает функцию setState(), которая извлекает самую последнюю версию из хранилища.
  • переместите dispatcher.dispatch() из ваших действий .js на ваш api.js(заменив return data);

Таким образом, ваш компонент изначально должен отобразить сообщение "загрузка" и обновить, как только данные с сервера будут находиться.

Ответ 3

Альтернативный метод - проверить, существует ли поддержка подписки перед тем, как вы будете играть с данными.

Попробуйте изменить свой код, чтобы выглядеть примерно так:

render: function(){

  var subscriptionPeriod = '';
  var subscriptionDue = ['',''];
  var subscriptionGenerated = '';

  if(this.props.subscription !== undefined){
       subscriptionPeriod = this.props.subscription.period;
       subscriptionDue = [this.props.subscription.due.to,this.props.subscription.due.from];
       subscriptionGenerated = this.props.subscription.generated;
  }

  return (
    <div >
        <h1>Statement</h1>
        From: {subscriptionPeriod[0]} - To {subscriptionPeriod[1]} <br />
        Due: {subscriptionDue}<br />
        Issued:{subscriptionGenerated}
    </div>
);
}

В функции рендеринга перед возвратом попробуйте добавить следующее:   if (this.props.subscription!= undefined) {   // Что-то здесь   }

Из-за того, что ваши данные изменяют состояние компонента верхнего уровня, он будет перезапускать рендер после того, как он будет иметь данные с определением подписки.

Ответ 4

Если я правильно понял, вы могли бы попробовать что-то вроде этого

// InitialActions.js


var InitialiseActions = {
initApp: function(){
    BillApi.getBill(function(result){
      // result from getJson is available here
      Dispatcher.dispatch({
          actionType: ActionTypes.INITIALISE,
          initialData: {
              bill: result
          }
      });
    });
}
};
module.exports = InitialiseActions;

//api.js

var BillApi = {
    getBillLocal: function() {
        console.log(biller);
        return biller;
    },
    getBill: function(callback) {
      $.getJSON('https://theurl.com/stuff.json', callback);
    }
};

$. getJSON не возвращает значение из запроса http. Это делает его доступным для обратного вызова. Здесь подробно объясняется логика: Как вернуть ответ от асинхронного вызова?

Ответ 5

Я отделяю свои действия, магазины и виды (компоненты React).

Прежде всего, я бы выполнил свое действие следующим образом:

import keyMirror from 'keymirror';


import ApiService from '../../lib/api';
import Dispatcher from '../dispatcher/dispatcher';
import config from '../env/config';


export let ActionTypes = keyMirror({
  GetAllBillPending: null,
  GetAllBillSuccess: null,
  GetAllBillError: null
}, 'Bill:');

export default {
  fetchBills () {
    Dispatcher.dispatch(ActionTypes.GetAllBillPending);

    YOUR_API_CALL
      .then(response => {
        //fetchs your API/service call to fetch all Bills
        Dispatcher.dispatch(ActionTypes.GetAllBillSuccess, response);
      })
      .catch(err => {
        //catches error if you want to
        Dispatcher.dispatch(ActionTypes.GetAllBillError, err);
      });
  }
};

Следующий мой магазин, поэтому я могу отслеживать все изменения, которые могут произойти во время моего вызова api:

class BillStore extends YourCustomStore {
  constructor() {
    super();

    this.bindActions(
      ActionTypes.GetAllBillPending, this.onGetAllBillPending,
      ActionTypes.GetAllBillSuccess, this.onGetAllBillSuccess,
      ActionTypes.GetAllBillError  , this.onGetAllBillError
    );
  }

  getInitialState () {
    return {
      bills : []
      status: Status.Pending
    };
  }

  onGetAllBillPending () {
    this.setState({
      bills : []
      status: Status.Pending
    });
  }

  onGetAllBillSuccess (payload) {
    this.setState({
      bills : payload
      status: Status.Ok
    });
  }

  onGetAllBillError (error) {
    this.setState({
      bills : [],
      status: Status.Errors
    });
  }
}


export default new BillStore();

Наконец, ваш компонент:

import React from 'react';

import BillStore from '../stores/bill';
import BillActions from '../actions/bill';


export default React.createClass({

  statics: {
    storeListeners: {
      'onBillStoreChange': BillStore
     },
  },

  getInitialState () {
    return BillStore.getInitialState();
  },

  onBillStoreChange () {
    const state = BillStore.getState();

    this.setState({
      bills  : state.bills,
      pending: state.status === Status.Pending
    });
  },

  componentDidMount () {
    BillActions.fetchBills();
  },

  render () {
    if (this.state.pending) {
      return (
        <div>
          {/* your loader, or pending structure */}
        </div>
      );
    }

    return (
      <div>
        {/* your Bills */}
      </div>
    );
  }

});

Ответ 6

Предполагая, что вы фактически получаете данные из своего API, но получаете слишком поздно, и сначала возникают ошибки, попробуйте следующее: В вашем контроллере-view.js добавьте следующее:

componentWillMount: function () {
    BillStore.addChangeListener(this._handleChangedBills);
},

componentWillUnmount: function () {
    BillStore.removeChangeListener(this._handleChangedBills);
},

_handleChangedBills = () => {
    this.setState({bill: BillStore.getAllBill()});
}

И в вашей функции getInitialState укажите пустой объект со структурой, которую ожидает ваш код (в частности, внутри него есть объект 'statement'). Что-то вроде этого:

getInitialState: function(){
return {
    bill: { statement:  [] }
};
},

Что происходит, когда вы получаете свое начальное состояние, оно неправильно извлекается из хранилища и возвращается объект undefined. Когда вы запрашиваете this.state.bill.statement, инициализируется счет, но undefined, и поэтому он не может найти ничего вызываемого оператора, поэтому вам нужно добавить его. После того, как у компонента было немного больше времени (это это асинхронная проблема, как говорят другие плакаты), она должна правильно загружаться из магазина. Вот почему мы ждем, когда магазин испустит изменения для нас, а затем мы возьмем данные из магазина.