web-dev-qa-db-ja.com

REACTおよびFLUXでAPI呼び出しを作成する方法

私は反応して変化するのが初めてで、サーバーからデータをロードする方法を理解するのに苦労しています。ローカルファイルから同じデータを問題なく読み込むことができます。

まず最初に、初期状態をビュー(view.js)に渡すこのコントローラービュー(controller-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 [〜#〜]データをロードするアクションファイルがあります。つまり、これはnotユーザーアクションによって呼び出されたデータですが、コントローラービューでは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

また、BillApi.getBillServer()にconsole.log(data)を追加したところ、データがサーバーから返されていることがわかります。しかし、表示されます[〜#〜]後[〜#〜]コンソールに警告が表示されますが、これは問題である可能性があります。誰かがいくつかのアドバイスを提供したり、それを修正するのを手伝ったりできますか?こんなに長い投稿をしてすみません。

[〜#〜]更新[〜#〜]

私はapi.jsファイルに変更を加えました(変更とDOMエラーplnkr.co/edit/HoXszori3HUAwUOHzPLGについてはこちらを確認してください)。問題の原因はプロミスの処理方法にあることが示唆されたためです。しかし、DOMエラーで確認できる問題と同じように見えます。

16
Jose the hose

これは非同期の問題です。 $.getJSON().then()を使用するだけでは不十分です。これはpromiseオブジェクトを返すので、api.getBill().then(function(data) { /*do stuff with data*/ });のようなことを実行して、呼び出し時にpromiseを処理する必要があります

私は 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);
  });
});
8
colinterface

あなたのコードから、意図されたフローは次のようなものです:

  • 一部のコンポーネントが初期化アクションを起動し、
  • 初期化アクション呼び出しAPI
  • サーバーからの結果を待ちます(ここで問題が発生すると思います:サーバーからの結果が返される前にコンポーネントのレンダリングが開始されます)。
  • 次に結果をストアに渡し、
  • 変化を放出し、
  • 再レンダリングをトリガーします。

典型的なフラックスのセットアップでは、これを多少異なるように構成することをお勧めします。

  • 一部のコンポーネントはAPIを呼び出します(しかしディスパッチャにまだアクションを起動していません
  • APIはgetJSONを実行し、サーバーの結果を待ちます
  • 結果が受信された後のみ、APIは受信されたデータでINITIALIZEアクションをトリガーします
  • ストアはアクションに応答し、結果で自身を更新します
  • その後、変化を放出します
  • 再レンダリングをトリガーする

私はjquery、promise、チェーンについてはあまり詳しくありませんが、これはおおまかにコードの次の変更に変換されると思います。

  • controller-viewにはストアへの変更リスナーが必要です。フラックスストアの変更にイベントリスナーを追加するcomponentDidMount()関数を追加します。
  • コントローラービューでは、イベントリスナーがsetState()関数をトリガーし、ストアから最新の_billをフェッチします。
  • dispatcher.dispatch()をactions.jsからapi.jsに移動します(return data);

このようにして、コンポーネントは最初に「読み込み中」のメッセージをレンダリングし、サーバーからのデータが入ったらすぐに更新する必要があります。

3
wintvelt

別の方法は、データを操作する前にサブスクリプションのプロパティが存在するかどうかを確認することです。

コードを次のように変更してみてください。

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){//ここで何かを実行します}

最上位コンポーネントの状態を変更するデータにより、サブスクリプションプロップが定義されたデータを取得すると、レンダリングが再トリガーされます。

2
Andrew Dover

アクション、ストア、ビュー(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>
    );
  }

});
2
mersocarlin

私が正しく理解していれば、次のようなことを試すことができます

// 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リクエストから値を返しません。コールバックで利用できるようにします。この背後にあるロジックについては、ここで詳しく説明します。 非同期呼び出しから応答を返す方法

2
giulp

実際に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は初期化されますが定義されていないため、ステートメントと呼ばれるものを見つけることができません。そのため、ステートメントを追加する必要があります。他のポスターが言ったように非同期の問題)、それはストアから適切にフェッチする必要があります。これが、ストアが変更を送信するのを待ってから、ストアからデータを取得する理由です。

0
Josh Baker