Как создавать вызовы 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, и поэтому он не может найти ничего вызываемого оператора, поэтому вам нужно добавить его. После того, как у компонента было немного больше времени (это это асинхронная проблема, как говорят другие плакаты), она должна правильно загружаться из магазина. Вот почему мы ждем, когда магазин испустит изменения для нас, а затем мы возьмем данные из магазина.