The Suspense is Killing Redux

blank

The Suspense is Killing Redux

譯文已獲得原作者授權,轉載時請附上原文鏈接medium.com/@ryanflorence/the-suspense-is-killing-redux-e888f9692430

在最近舉辦的workshop上我一直考慮這個問題:

Suspense 會殺死Redux 嗎?

不得不說上面的表達方式過於粗魯,但我認為Suspense 即將取代Redux。

React Suspense主要用來處理異步數據請求時的頁面渲染,這方面是Redux未曾涉及的,但為了做到這一點, Suspense需要去接管客戶端的數據邏輯,而這也正是Redux一直以來所擅長的。

如果你還沒有聽說過React Suspense ,可以先花點時間聽下Dan Abramov在JSConf冰島上的關於React Suspense的分享

Suspense 的基本用法

使用Suspense分成三個部分:緩存,資源請求和組件。

緩存範例:

import{createCache}from'react-cache'letcache=createCache()

資源請求範例:

import{createResource}from'react-cache'letInvoiceResource=createResource((id)=>{returnfetch(`/invoices/${id}`).then(response=>{returnresponse.json()})})

它僅需要接受一個返回promise 的函數。如下是一個使用了上方的資源請求和緩存的組件範例:

importcachefrom'./cache'importInvoiceResourcefrom'./InvoiceResource'letInvoice=({invoiceId})=>{letinvoice=InvoiceResource.read(cache,invoiceId)return(<h1>{invoice.number}</h1>)}

瞧,這就是Suspense

這個應用的渲染過程的步驟如下:

  1. React 開始渲染(在內存中)。
  2. 調用InvoicesResource.read()
  3. invoiceId對應的緩存數據不存在,從而觸發createResource函數的調用,發送異步請求去獲取數據。
  4. 然後,緩存會throw第3步中返回的promise。 (這裡能throw的除了異常,也可以是其他東西,甚至可以是window對象)這個promise被React捕獲之後,會暫停渲染過程。
  5. React 等待異步請求結果(等待promise resolve)。
  6. 異步請求完成(promise resolve)。
  7. React嘗試再次渲染Invoices (在內存中)。
  8. 再次調用InvoicesResource.read()
  9. 此時已有數據緩存, ApiResource.read()同步返回數據。
  10. React 渲染出頁面。

上方的步驟中,我們將異步的資源請求邏輯當做同步函數調用,用起來非常炫酷。

而關於資源請求時如何處理佔位符和Spinner,就不在這裡展開討論,我們只需要知道:React 將會維持在老的頁面,直到新的數據加載完成,當資源請求時間超時,可以先去渲染佔位符。

那麼在Redux 中又是如何工作呢?

在redux 中執行相同的流程的話,它會是這樣的:

/////////////////////////////////////////////// the store and reducerimport{createStore}from'redux'import{connect}from'react-redux'letreducer=(state,action)=>{if(action.type==='LOADED_INVOICE'){return{...state,invoice:action.data}}returnstate}letstore=createStore(reducer)/////////////////////////////////////////////// the actionfunctionfetchInvoice(dispatch,id){fetch(`/invoices/${id}`).then(response=>{dispatch({type:'LOADED_INVOICE',data:response.json()})})}/////////////////////////////////////////////// the component, all connected upclassInvoiceextendsReact.Component{componentDidMount(){fetchInvoice(this.props.dispatch,this.props.invoiceId)}componentDidUpdate(prevProps){if(prevProps.invoiceId!==this.props.invoiceId){fetchInvoice(this.props.dispatch,this.props.invoiceId)}}render(){if(!this.props.invoice){returnnull}return<h1>{invoice.number}</h1>}}exportdefaultconnect(state=>{return{invoices:state.invoices}})(Invoices)

對比一下

它們的工作方式不同,但也有些部分是相同的:

  • store → cache
  • mapStateToProps → Resource.read()
  • action → function passed to resource

reducer 和dispatch 函數不復存在,因為已不再需要通過action 來更新state,而是直接從資源請求中讀取數據。當(緩存中)沒有數據時,會暫停渲染並等待數據請求返回。在某種程度上,cache + resources 同時充當了dispatch,reducers 和actions 的作用。

另外使用Suspense時不需要使用生命週期hook函數。當invoiceId改變而觸發Invoice重新渲染時,若該invoiceId對應的緩存數據為空,則會自動發送新請求去獲取數據,這比在生命週期hook函數中監聽props的變化再dispatch一個action要簡單得多。

依我看來,無論你之前是習慣使用Redux store還是組件層的local state,切換到Suspense都會毫無壓力。

因此,如果你使用Redux 主要用來獲取服務端數據並渲染的話,那麼Suspense 完全可以替換掉Redux。我會用Suspense,因為它能讓代碼更簡單,更好處理加載中的狀態。

緩存失效怎麼辦

createCache的第一個參數就是一個用於處理失效的函數。我測試下來發現,一旦緩存數據更新,頁面就能根據新的數據直接重新渲染。這能客戶端數據緩存和服務器數據保持同步,看起來是不是很酷。

此外, Suspense處理頁面更新的方式感覺很棒:新頁面在內存中渲染時,舊頁面仍然存在並且是可交互的,當數據更新後,頁面將使用新數據(來自服務端)重新渲染,要知道通常服務端渲染也是這樣工作的,這種方式也會讓同步客戶端和服務器數據的中間層的存在失去意義。

Suspense無法取代一切

有些人正在使用Redux做更複雜的事情(比如將狀態保持與API同步的同時也存到localStorage中), Suspense也不能取代Redux的所有應用場景。

但是,依我看來,我所接觸的大多數正在使用Redux的人,僅僅是用來獲取服務器端的資源數據,如果你也是其中一員的話,我想你會愛上Suspense的易用性和用戶體驗。

參加workshop

本文作者Ryan Florence正在美國地區舉辦React相關workshop, 點擊查看workshop城市和日期

譯者後記

本文中提到的例子,目前已可以使用最新發布的react v16.6版本體驗, react-cache使用canary版本( react-cache API極不穩定,最新發布的2.0.0-alpha.0 API已改,請慎重使用),完整範例代碼可參考foryuki/suspense-sample

更新

react-cache 2.0.0-alpha版本中移除了createCache方法,API從原來的Resource.read(cache, key)變成Resource.read(key) ,內置了cache。詳請見issue:react-cache Remove cache as argument to read 。完整範例代碼可參考: github.com/foryuki/susp

What do you think?

Written by marketer

blank

Windows10開機動畫的純CSS3實現

blank

如何通過CRDT改進Vue應用? PostCSS作者Andrey告訴你