如何在REACT和FLUX

时间:2015-10-06 22:11:29

标签: reactjs reactjs-flux

我有新的反应和变化,我正在努力弄清楚如何从服务器加载数据。我能够从本地文件加载相同的数据而没有任何问题。

首先我有这个控制器视图(controller-view.js),它将初始状态传递给视图(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()时,我在控制台中得到了followind错误......

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(),我可以看到数据是从服务器返回的。但它显示 AFTER 我在控制台中收到警告,我认为可能是问题。有人可以提供一些建议或帮我解决吗?对不起这么长的帖子。

更新

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

6 个答案:

答案 0 :(得分:8)

这是一个异步问题。使用$.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);
  });
});

答案 1 :(得分:3)

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

  • 某个组件触发初始化操作,
  • 初始化操作调用API
  • 等待来自服务器的结果(我认为这是事情发生的地方:你的组件渲染在服务器的结果恢复之前就开始了),
  • 然后将结果传递给商店,
  • 发出更改
  • 触发重新渲染。

在典型的光通量设置中,我建议将其结构略有不同:

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

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

  • controller-view需要更改商店的监听器:添加一个componentDidMount()函数,该函数将一个事件监听器添加到flux存储库更改中。
  • 在controller-view中,事件侦听器触发setState()函数,该函数从商店中获取最新的_bill。
  • dispatcher.dispatch()从您的actions.js移至您的api.js(替换return data);

这样,您的组件最初应该呈现一些“加载”消息,并在服务器的数据进入后立即更新。

答案 2 :(得分:2)

另一种方法是在播放数据之前检查订阅的支柱是否存在。

尝试修改代码看起来像这样:

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

在返回之前的render函数中尝试添加以下内容:     if(this.props.subscription!= undefined){     //在这做点什么     }

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

答案 3 :(得分:2)

如果我理解正确,你可以试试这样的事情

// 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?

答案 4 :(得分:2)

我将我的动作,商店和视图(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>
    );
  }

});

答案 5 :(得分:0)

假设您实际上是从API获取数据,但是得到的时间太晚并且首先抛出错误,请尝试以下操作: 在controller-view.js中,添加以下内容:

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

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

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

在你的getInitialState函数中,给出一个具有你的代码所期望的结构的空对象(具体来说,在其中有一个&#39;语句&#39;对象)。像这样:

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

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