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

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

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

.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;

вид.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;

а это магазин магазине.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

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

обновление

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

6 ответов


Это асинхронная проблема. Используя $.getJSON().then() не хватает. Поскольку он возвращает объект promise, вы должны обрабатывать обещание при вызове, делая что-то вроде 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);
  });
});

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

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

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

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

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

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

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


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

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

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>
);
}

в функции рендеринга перед возвратом попробуйте добавить следующее: если(это.реквизит.подписка != неопределенный){ // сделайте что-нибудь здесь }

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


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

// 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-запроса. Это делает его доступным для обратного вызова. Логика этого подробно объясняется здесь:как вернуть ответ от асинхронного вызова?


Я разделю свои действия, магазины и представления (компоненты 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>
    );
  }

});

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

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

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

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

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

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

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