2016-06-10 105 views
34

我們有一個取得對象異步的動作,我們稱之爲getPostDetails,它需要一個參數,以便通過 獲取一個id。用戶會看到一個帖子列表,點擊其中一個可以獲得一些細節。如何在使用Redux獲取數據時避免競爭條件?

如果用戶點擊「Post#1」,我們會發送一個GET_POST動作,看起來像這樣。

const getPostDetails = (id) => ({ 
    type: c.GET_POST_DETAILS, 
    promise: (http) => http.get(`http://example.com/posts/#${id}`), 
    returnKey: 'facebookData' 
}) 

這是通過一箇中間件,它增加了成功處理程序的承諾,這將調用像 GET_POST__OK動作與反序列化JSON對象回升。減速器查看該對象並將其應用於商店。一個典型的 __OK減速機看起來像這樣。

[c.GET_ALL__OK]: (state, response) => assign(state, { 
    currentPost: response.postDetails 
}) 

後來下了線,我們有一個看起來在currentPost,並顯示當前職位的細節部分。

但是,我們有一個競賽條件。如果用戶一個接一個地提交兩個GET_POST_DETAILS動作,則不能保證我們在什麼順序下接收到__OK動作,如果第二個http請求在第一個http請求之前完成,則 狀態將變得不正確。

Action     => Result 
    --------------------------------------------------------------------------------- 
|T| User Clicks Post #1  => GET_POST for #1 dispatched => Http Request #1 pending 
|i| User Clicks Post #2  => GET_POST for #2 dispatched => Http Request #2 pending 
|m| Http Request #2 Resolves => Results for #2 added to state 
|e| Http Request #1 Resolves => Results for #1 added to state 
V 

我們如何確保用戶點擊的最後一項始終優先?

回答

71

該問題是由於狀態組織不理想。

在Redux應用程序中,類似currentPost這樣的狀態鍵通常是反模式。如果每次導航到另一個頁面時必須「重置」狀態,則會丟失main benefits of Redux (or Flux):緩存中的一個。例如,如果任何導航重置狀態並重新提交數據,則不能再立即導航回去。

一種更好的方式來存儲這些信息將分開postsByIdcurrentPostId

{ 
    currentPostId: 1, 
    postsById: { 
    1: { ... }, 
    2: { ... }, 
    3: { ... } 
    } 
} 

現在你可以在同一時間,只要你喜歡獲取儘可能多的職位,並獨立地將它們合併到postsById緩存,而不擔心提取的帖子是否是當前的帖子。

在組件內部,您總是會讀取state.postsById[state.currentPostId]或更好,從reducer文件導出getCurrentPost(state)選擇器,以便組件不依賴於特定的狀態形狀。

現在沒有競賽條件你有一個帖子緩存,所以你不必在你回去時重新提取。如果您希望通過URL欄控制當前帖子,則可以完全從Redux狀態中刪除currentPostId,而是從路由器中讀取它 - 其餘邏輯將保持不變。


儘管這不完全相同,但我碰巧遇到了另一個類似問題的例子。查看code beforecode after。這不是相當於與您的問題相同,但希望它顯示了國家組織如何幫助避免競爭條件和不一致的道具。

我還錄製了一個免費的視頻系列,解釋了這些話題,所以你可能想要check it out

+0

我有一點不同的情況。我應該怎麼做,如果我也有'currentPostId'和'postById',但我必須在時間(創建時間,預覽圖片,元等等)獲取有關所有帖子的數據,然後獲取僅一個選定帖子的內容,在'postById'中緩存與這篇文章有關的所有內容? – denysdovhan

+0

@DanAbramov嗨,我知道這是一個相當古老的問題,但由於任何動作創建者都無法訪問狀態,動作創建者如何判斷它是否被緩存或需要獲取遠程對象? – Vinz243

+0

如果你有'currentListOfSearchResults'這是一個數組,而不是一個'currentPost'?我通常最終會跳過Redux獲取這些結果,並將它們置於本地頁面組件的狀態。 –

3

丹的解決方案可能是更好的解決方案,但另一種解決方案是在第二個請求開始時放棄第一個請求。

您可以通過將您的動作創建者分成異步的動作創建者來執行此操作,該動畫創建者可以從存儲中讀取並分派其他動作,這可以通過redux-thunk來完成。

您的異步操作創建者應該做的第一件事是檢查商店是否存在現有承諾,如果存在承諾則將其中止。如果不是,它可以發出請求,併發送一個'請求開始'動作,其中包含承諾對象,該對象將在下次存儲。

這樣,只有最近創建的承諾才能解決。如果有人這樣做,你可以派發一個成功的行動與收到的數據。如果承諾因某種原因被拒絕,您也可以發送錯誤操作。

+5

http://i.kinja-img.com/gawker-media/image/upload/aoz8kgx8pzknypz7z38n.jpg –

+5

請注意,我們不建議在狀態中存儲承諾。理想情況下,它應該是可序列化的。不過,我確實描述了你在[本課]中建議的方法(https://egghead.io/lessons/javascript-redux-avoiding-race-conditions-with-thunks?course = building-react-applications-with-慣用-終極版)。這是一個很好的方法,但不能取代正常狀態。 (結合這兩種方法是一個好主意。) –