【问题标题】:How to create API calls in REACT and FLUX如何在 REACT 和 FLUX 中创建 API 调用
【发布时间】:2016-01-04 00:04:31
【问题描述】:

我是反应和变化的新手,我很难弄清楚如何从服务器加载数据。我可以毫无问题地从本地文件加载相同的数据。

所以首先我有这个控制器视图 (controller-view.js),它将初始状态传递给视图 (view.js)

controller-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

我还在 BillApi.getBillServer() 中添加了一个 console.log(data) ,我可以看到数据是从服务器返回的。但它显示在 AFTER 我在控制台中收到警告,我认为这可能是问题所在。任何人都可以提供一些建议或帮助我解决它吗?抱歉发了这么长的帖子。

更新

我对 api.js 文件进行了一些更改(在此处查看更改和 DOM 错误 plnkr.co/edit/HoXszori3HUAwUOHzPLG ),因为有人认为问题是由于我处理承诺的方式造成的。但它似乎仍然是您在 DOM 错误中看到的相同问题。

【问题讨论】:

  • subscriptionsList 你在传递什么?它正在寻找this.props.subscriptions,但它不存在,所以你得到Cannot read property 'period' of undefined。我的猜测是你也有某种类型的竞争条件。 Flux 本质上是异步的……
  • 我想也许这就是我收到“无法读取”错误的原因 - 因为竞争条件。数据可能还没有加载?任何提示如何解决这个问题?
  • 是的,您可以使用建议的ultralame 之类的回调方法,或者您可以给_bill 一个默认对象,例如var _bill = { subscriptions: [] },所以当您执行getInitialState 时,您只需通过bill store.getAllBill()。然后当组件挂载时,数据被获取,存储将发出更改并更新您的状态

标签: reactjs reactjs-flux


【解决方案1】:

这是一个异步问题。使用$.getJSON().then() 是不够的。由于它返回一个 Promise 对象,因此您必须在调用时通过执行类似 api.getBill().then(function(data) { /*do stuff with data*/ }); 的操作来处理该 Promise

我使用以下代码创建了CodePen example

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

【讨论】:

  • 感谢您的回答。但是,如果您检查我使用 getBillServer 调用服务器的 api.js,您可以看到我正在使用 .then() 并将数据返回给操作。这不正确吗?
  • 更新了我的答案。基本上,第一个 .then() 返回一个承诺,所以你必须使用另一个 .then() 来获取你的数据。
  • 非常感谢。我现在只是在尝试。为了清楚起见,在这种情况下,searchSpotify 函数将进入我的 api.js 文件,而函数调用 (searchSpotify('donald trump')) 将进入我的操作文件?
  • 不怕。我创建了一个 plunkr,所以你可以看到我是如何添加代码的。我还添加了 dom 错误,因此您可以看到它正在到达 api。但似乎组件在数据到达之前加载。我在其他地方一定有问题 - plnkr.co/edit/HoXszori3HUAwUOHzPLG
【解决方案2】:

从您的代码看来,预期的流程类似于:

  • 某些组件触发初始化操作,
  • 初始化动作调用 API
  • 等待来自服务器的结果(我认为这就是问题所在:您的组件渲染在来自服务器的结果返回之前开始),
  • 然后将结果传递给商店,
  • 发出变化和
  • 触发重新渲染。

在典型的助焊剂设置中,我建议将其结构有所不同:

  • 某些组件调用 API(但尚未向调度程序触发操作
  • API 执行 getJSON 并等待服务器结果
  • 只有在收到结果后,API 才会使用收到的数据触发 INITIALIZE 操作
  • 商店响应操作,并根据结果更新自身
  • 然后发出变化
  • 触发重新渲染

我对 jquery、promises 和 chaining 不太熟悉,但我认为这大致会转化为您的代码中的以下更改:

  • controller-view 需要一个更改监听器来存储:添加一个componentDidMount() 函数,该函数将事件监听器添加到通量存储更改。
  • 在控制器视图中,事件侦听器触发setState() 函数,该函数从商店中获取最新的_bill。
  • dispatcher.dispatch() 从您的 actions.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>
    );
    }
    

    在返回之前的渲染函数中尝试添加以下内容: 如果(this.props.subscription != 未定义){ // 在这里做点什么 }

    由于您的数据改变了顶级组件的状态,一旦它拥有定义了订阅道具的数据,它将重新触发渲染。

    【讨论】:

    • 谢谢。我将把这段代码放在哪里?我认为它不应该将渲染函数包装在控制器视图中?
    • 非常感谢您编写代码。我可以看到这是一个更好的方法。但问题仍然存在于未加载初始数据的地方。我仍然收到错误“警告:道具类型失败:subscriptionsList 中未指定所需道具subscription。检查viewBill 的渲染方法。”我猜当数据加载并且重新触发没有发生时,状态没有改变。我可以看到数据已加载到控制台窗口中。有什么想法可能导致它吗?
    【解决方案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 请求返回值。它使它可用于回调。 这背后的逻辑在这里详细解释:How to return the response from an asynchronous call?

    【讨论】:

      【解决方案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);
            });
        }
      };
      

      接下来是我的 Store,所以我可以跟踪我的 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 获取数据,但获取数据太晚并且首先引发错误,请尝试以下操作: 在您的 controller-view.js 中,添加以下内容:

        componentWillMount: function () {
            BillStore.addChangeListener(this._handleChangedBills);
        },
        
        componentWillUnmount: function () {
            BillStore.removeChangeListener(this._handleChangedBills);
        },
        
        _handleChangedBills = () => {
            this.setState({bill: BillStore.getAllBill()});
        }
        

        并且在您的 getInitialState 函数中,提供一个具有您的代码期望的结构的空对象(具体来说,在其中有一个“语句”对象)。像这样的:

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

        发生的情况是,当您获取初始状态时,它没有正确地从存储中获取,因此将返回一个未定义的对象。然后当您请求 this.state.bill.statement 时,bill 已初始化但未定义,因此它找不到任何称为 statement 的内容,因此您需要添加它。在组件有更多时间之后(这是一个像其他海报所说的异步问题),它应该从商店正确获取。这就是为什么我们等待 store 为我们发出更改,然后我们从 store 中获取数据。

        【讨论】:

          猜你喜欢
          • 2017-01-13
          • 1970-01-01
          • 2016-06-08
          • 2022-07-05
          • 1970-01-01
          • 2016-04-22
          • 2015-03-21
          • 2016-03-04
          • 2018-03-05
          相关资源
          最近更新 更多