2015-10-06 21 views
16

我是新來的反應和流量,我很難弄清楚如何從服務器加載數據。我可以從本地文件加載相同的數據,而不會出現問題。如何在REACT和FLUX中創建API調用

信息首先我有向下傳遞初始狀態的視圖此控制器視圖(控制器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; 

這是店裏因此,正如我前面提到的,當我加載數據在本地使用BillApi.getBillLocal()的行動,一切工作正常 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.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我得到的控制檯,我認爲可能是問題的警告。任何人都可以提供一些建議或幫助我解決它?對不起,這麼長的帖子。

UPDATE

我做了一些更改api.js文件(點擊此處變革和DOM錯誤plnkr.co/edit/HoXszori3HUAwUOHzPLG),因爲它是建議,這個問題是由於我如何處理承諾。但它仍然是你在DOM錯誤中看到的同樣的問題。

+0

你在傳遞什麼'subscriptionsList'?它正在尋找'this.props.subscriptions',並且它不存在,所以你得到'不能讀取'undefined'屬性'時期'。我的猜測是你也有一些類型的競爭條件。通量本質上是異步的... –

+0

我想也許多數民衆贊成爲什麼我得到'無法讀取'的錯誤 - 由於競爭條件。數據可能尚未加載?任何提示如何解決這個問題? –

+1

是的,你可以使用superlame建議的回調方法,或者你可以給'_bill'一個默認對象,比如'var _bill = {subscriptions:[]}',所以當你執行'getInitialState'時,你只需要'bill'通過'store.getAllBill()'。那麼當組件裝入時,數據被提取,並且商店將發出更改並更新您的狀態 –

回答

8

這是一個異步問題。使用$.getJSON().then()是不夠的。由於它返回一個承諾的對象,你必須做一些像api.getBill().then(function(data) { /*do stuff with data*/ });

以處理調用的承諾我做了一個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); 
    }); 
}); 
+0

感謝您的回答。但是,如果您檢查api.js在哪裏使用getBillServer調用服務器,您可以看到我正在使用.then()並將數據返回給操作。這是不正確的? –

+0

更新了我的答案。基本上,第一個'.then()'返回一個promise,所以你必須使用另一個'.then()'來獲取你的數據。 – ultralame

+0

非常感謝。我現在只是在嘗試。要明確一點,在這種情況下,searchSpotify函數會進入我的api.js文件和函數調用(searchSpotify('donald trump'))到我的動作文件中? –

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

在返回之前渲染功能嘗試添加以下內容: 如果(!this.props.subscription =未定義){// 在這裏做些什麼 }

由於您的數據改變了頂層組件的狀態,所以一旦它具有定義了訂閱道具的數據,它將重新觸發渲染。

+0

謝謝。我會在哪裏放置這段代碼?我認爲它不應該在控制器視圖中包裝渲染功能? –

+0

請參閱我對代碼的修改 –

+0

非常感謝您編寫代碼。我可以看到這是一個更好的方法。但問題仍然存在於初始數據未加載的位置。我仍然收到錯誤「Warning:失敗的propType:必需的prop'subscription'沒有在'subscriptionsList'中指定。檢查'viewBill'的渲染方法。」我猜在數據加載和重新觸發沒有發生時狀態不會改變。我可以看到數據已加載到控制檯窗口中。任何想法可能會導致它? –

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?

3

它看起來像你的代碼,預期流動是一樣的東西:

  • 一些組件火災初始化動作,
  • 初始化操作調用API
  • 等待服務器的結果(我認爲這裏是事情發生的地方:你的組件渲染在服務器返回結果之前開始),
  • 然後將結果傳遞給商店,
  • 發出變化並且
  • 觸發重新呈現。

在一個典型的流量設置,我會建議構建這個有點不同:

  • 一些組件調用API(但不會觸發行動調度員尚未
  • API做getJSON並等待服務器結果
  • 只有收到結果後,API觸發與接收到的數據
  • 店響應行動,並與結果更新自身的初始化動作
  • 然後發出變化
  • 觸發重新渲染

我不是那麼熟悉的jQuery ,承諾和鏈接,但我認爲這大致會轉化爲您的代碼中的以下更改:

  • 控制器視圖需要更改偵聽器到商店:添加一個componentDidMount()將事件偵聽器添加到磁通存儲更改的功能。
  • 在控制器視圖中,事件偵聽器觸發一個setState()函數,該函數從存儲中獲取最新的_bill。
  • dispatcher.dispatch()從您的actions.js移動到您的api.js(替換return data);

這樣一來,你的組件最初應該使一些「裝載」的消息,並儘快更新來自服務器的數據是。

2

我會分開我的動作,存儲和訪問量(反應的組分)。

首先,我會實現我的操作是這樣的:

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

}); 
0

假設你實際上是從你的API獲取數據,但得到它爲時已晚,首先將錯誤拋出,試試這個: 在你的控制器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的東西,所以你需要添加它。在組件有更多時間之後(這是一個像其他海報說的異步問題),它應該從商店中正確取貨。這就是我們等待商店爲我們發佈變更的原因,然後我們從商店中獲取數據的原因。

相關問題