如何評價React的新功能Time Slice 和Suspense?
各位看官好,方正,來了。
自從Dan老哥加入React team以後,React帶來了很多變化,其中Time Slicing和Suspense是Dan老哥在「Beyond React 16 by Dan Abramov - JSConf Iceland」上演講的重要題目。
這兩點主要用來對付兩個問題:CPU速度和網路IO
就讓我來給大家解釋一下,這兩個玩意兒。
Time Slicing
以下是解釋
時間分片
- React 在渲染(render)的時候,不會阻塞現在的線程
- 如果你的設備足夠快,你會感覺渲染是同步的
- 如果你設備非常慢,你會感覺還算是靈敏的
- 雖然是非同步渲染,但是你將會看到完整的渲染,而不是一個組件一行行的渲染出來
- 同樣書寫組件的方式
也就是說,這是React背後在做的事情,對於我們開發者來說,是透明的,具體是什麼樣的效果呢?
親愛的Dan為我們做了一個有趣的Demo:
這個組件非常的巨大,而且在輸入框每次輸入東西的時候,就會進去一直在渲染。為了更好的看到渲染的性能,Dan為我們做了一個表。
我們先看看,同步模式:
同步模式下,我們都知道,我們沒輸入一個字元,React就開始渲染,當React渲染一顆巨大的樹的時候,是非常卡的,所以才會有shouldUpdate的出現,在這裡Dan也展示了,這種卡!
我們再來看看第二種(Debounced模式):
Debounced模式簡單的來說,就是延遲渲染,比如,當你輸入完成以後,再開始渲染所有的變化。
這麼做的壞處就是,至少不會阻塞用戶的輸入了,但是依然有非常嚴重的卡頓。
切換到非同步模式:
非同步渲染模式就是不阻塞當前線程,繼續跑。在視頻里可以看到所有的輸入,表上都會是原諒色的。
(註:簡單的來說,react的非同步渲染其實就是拉長了render的時間,一次跑一點,所以機器性能很差的,會看到react渲染時有稍微的延遲,但是不是卡頓。)
Suspense
Suspense主要解決的就是網路IO問題。網路IO問題其實就是我們現在用Redux+saga等等一系列亂七八糟的庫來解決的「副作用」問題。
這一次,這些副作用,React官方終於出手來幫我們做了,我們來看一下Dan在演示中是怎麼做的。
這是一個很正常的操作,接下來我們看看新的Suspense的做法。
解釋一下,fetchMovieDetails是一個返回promise的api,類似我們的fetch封裝一下。然後注意看
const movie = movieDetailsFetcher.read(props.id)
這行代碼,這行代碼Dan說了,非常的具有爭議性。這行代碼做的與之前的
const movie = movieDetailsJSON[props.id]
是一樣的效果,只不過是從網路中拿來的,會有延遲。
然後拿到數據以後,我們調用一個新的api叫做
this.deferSetState(...);//之前的setState非同步新版本
來綁定數據。
這就與我們之前書寫組件的方式有很大的差異,因為我們知道,在react 的 render()函數中,非同步去fetch數據是沒用的,因為非同步函數會在下一個JS事件循環中才會進行,此時,已經渲染完畢了。所以拿到數據了也沒用。
調用render函數-&>發現有非同步請求-&>懸停,等待非同步請求結果-&>再渲染展示數據
由此可以看到,我們可以用一種同步的方式去書寫代碼,就像我們寫async/await一樣!是不是非常的爽!?這就是Suspense的核心功能
那麼我們來解釋一下Suspense
懸停(也可以叫做暫停)
- 引入新的api,可以使得任何state更新暫停,直到條件滿足時,再渲染(像async/await)
- 可以在任何一個組件里放置非同步獲取數據,而不用做多餘的設置
- 在網速非常快的時候,可設置,整個數據到達Dom,更新完畢以後再渲染
- 在網速非常慢的時候,可設置,精確到單個組件的等待、以及更新,然後再渲染
- 會給我們提供 high-level (createFetcher)和 low-level( Placeholder, Loading ) 的 API,可以供給業務代碼和一些小組件的書寫。
總結一下:React的未來是什麼?
我們回顧一下react 16給我們帶來的一些列重要變化:
- render/純組件能夠return任何數據結構,以及CreatePortal Api
- 新的context api,嘗試代替一部分redux的職責
- babel的&<&>操作符,方便用戶返回數組
- 非同步渲染/時間切片(time slicing),成倍提高性能
- componentDidCatch,錯誤邊界,框架層面上提高用戶debug的能力
- 未來的Suspense,優雅處理非同步副作用
這麼一些列的變化,我們可以清晰的看到,redux作者,現任react的大哥dan,以及其背後的團隊,正在嘗試將React打造成一個「大而全」的框架,而不是像原來那樣,讓社區自己發展,React只做view層。
對於熟練的開發者來說,這是非常非常棒的事情,但是同時,react的api也開始慢慢多了起來,對於初學者來說,是一個不友好的信號。
1
回過頭來說,Time Slice,得益於fiber,使得在執行任務的期間可以隨時暫停,跑去干別的事情,這個特性使得react能在性能極其差的機器跑時,仍然保持有良好的性能。
在很大程度上,我們即使不手動優化react,我們也能夠獲得非常好的性能表現,這對於不要過早優化的項目來說,是一個非常好的信號,如創業公司的項目。
2
Suspense功能想解決從react出生到現在都存在的「非同步副作用」的問題,而且解決得非常的優雅,使用的是「非同步但是同步的寫法」,我個人認為,這是最好的解決非同步問題的方式。
調用render函數-&>發現有非同步請求-&>懸停,等待非同步請求結果-&>再渲染展示數據
總的來說,框架的拼比,已經從「代碼易維護」-&>「性能之戰」-&>「快速構建應用」演變到了如今「快速構建高性能並且易維護應用」。
今早阮一峰老師轉發了一則新聞,說 Dan宣布,Rip Redux(安息吧Redux)。其實,Dan在演講中並沒有這麼說,並沒有這麼說,並沒有這麼說,他只是說「某些問題不用redux去解決了」。
參考文獻:
Sneak Peek: Beyond React 16 - React Blog?reactjs.orgTime Slice 很早之前就公布了。可以從各種 demo 里看到很明顯的性能提升,非常厲害!
不過這次 Talk 中更讓人激動的應該說是 Suspense 了,Dan 幾天前就在 twitter 里製造「懸念」:
"things that would take me hours to build before, I can now do in 30 minutes."
直到昨晚的 Talk,」懸念「 被揭開:
我們可以看一下 demo 中的示例代碼:
import { createFetcher, Placeholder, Loading } from ../future;
// ...
const movieDetailsFetcher = createFetcher(
fetchMovieDetails
);
function MovieDetails(props) {
const movie = movieDetailsFetcher.read(props.id);
return (
&{movie.title}&
}
通過 」來自未來「 的幾個 API,達到了這樣的效果。fetchMovieDetails 是返回 Promise 的非同步方法,在通過 createFetcher 的包裝之後,就可以直接在純函數組件的 render 方法里,同步的調用 read 方法獲得數據,再正常 render。
這看起來」違背「了 React 的常識:不能在 render 中發起請求。但 Dan 告訴我們,先試試看效果。運行效果當然是非常完美的。
這是咋跑起來的?
Andrew Clark 在 Talk 結束後公布了一些相關信息,推薦大家直接去看看。大致意思是這樣的:
- 在 render 方法中,讀取 緩存 中的值
- 如果緩存中存在這個值,render 方法會正常執行
- 如果緩存中沒有這個值,緩存 會拋出一個異常,異常內容是一個 Promise
- 當這個 Promise resolve 的時候,React 會從它之前停下的地方開始重試
- 上面提到的緩存是通過前不久公布的新 context API 來實現的
Placeholder 和 Loading
接下來,Dan 還給我們展示了兩個更接近真實業務場景的組件。Placeholder 可以通過 delayMs 和 fallback 的配合在 fetcher 的數據還未獲取到時做過渡。而 Loading 可以通過 render prop 來告訴你當前 fetcher 的 loading 狀態。很難準確的通過語言把 demo 實現的效果表達出來,強烈建議去看看演示的視頻。
Async
createFetcher 可以做的其實遠不止數據的獲取,demo 還進一步演示了 Code split 和 image 載入這兩個非同步的渲染問題。
在 createFetcher 中直接使用動態 import 獲取組件
在 createFetcher 中等待圖片的載入來防止 render 時因為圖片載入產生的高度抖動
Suspense
Dan 把這個 feature 稱為 Suspense,應該是取它 「懸念」 這個含義的雙關,和 「暫停,懸停」 的意思。有了它,寫 React 可以變成這樣。
- 在數據還未準備好時,暫停狀態的更新
- 對任何組件都可以輕鬆添加非同步的數據,不會產生阻塞
- 網速快的時候,在整個 tree ready 的時候直接 render
- 網速不好的時候,可以精確控制 loading 的狀態
- 會給我們提供 high-level 和 low-level 的 API,可以有 Placeholder, Loading 這樣直接在業務場景中的組件,也會有給庫使用的底層 API
Time Slice其實就是React v16還是beta時宣傳的Fiber的直接應用,參照 React Fiber是什麼 ,這沒有什麼讓人驚喜的,真正讓人掉下巴的是Suspense。
之前,React一直關注於渲染,根據數據產生視圖,不摻和如何獲取數據,尤其不關心如何通過AJAX獲取數據,導致數據獲取的方式千奇百怪,很不統一。現在有了Suspense,是React終於涉足於數據獲取的方案,而且真的非常巧妙。
將來的code基本上類似這樣:
import createFetcher from future-react;
const fetchSomething = ... // AJAX的實現
const SomethingFetcher = createFetcher(fetchSomething);
render() { 注意看,fetch的過程是非同步的,但是上面的代碼卻是同步的,而且也沒有用async/await。
const data = SomethingFetcher.read();
return &
}
背後的原理,是createFetcher產生的SomethingFetcher在被調用read時,看看cache里是不是有結果了,如果有,就同步返回;如果沒有,那就throw出一個Promise(注意不是throw出Error),這個Promise被React捕獲之後,就會暫停渲染過程,等到這個Promise對象resolve的時刻再重新開始渲染,而這個時候數據也已經獲得了,所以data也就有值了。
不得不說這招真是非常巧妙,表面上看,throw會產生性能問題,但是在渲染過程中throw的代價應該是九牛一毛。
先提供一個我隨便玩了玩Suspense的文章
張立理:超簡化的React Suspense實現?zhuanlan.zhihu.comTime Slicing是Fiber的完全體形態,這個我不作過多的分析,更多地是談一下其它部分的觀點:
首先往大了說,從這一刻開始是React與其它React Compatible框架的的分水嶺。之前由於React的精簡,能簡單地衍生出完全與React兼容的框架,如preact、inferno、qreact、nerv等,但從現在開始,React在其新的架構上開始堆疊功能,如果要繼續保持React兼容,這些框架要投入的精力將直線上升。那麼你是不是能跟得住Facebook的腳步呢?
然後,Suspense的最基礎功能是在當下直接能實現的,只要有Error Boundary就行,如我上面的文章中已經給了示例。因此如果你真的覺得Suspense對你很有幫助,而且也只是要做一下「在render里搞個非同步大新聞」的功能,那麼直接我行我上實現一個用得了。
回歸Suspense本身,我對其的基礎評價是:這是一個實現特定場景下的高效開發的利器,但並沒有神奇到值得你鼓掌。
先來說特定,這樣的開發模式意味著如果你要使用Suspense,你將放棄:
- 較遠距離的組件間的數據共享,因為沒有全局狀態的支持。更多的時候,Suspense用在數據獨立的場景下。當然你可以硬生生套回到全局狀態上去,但這時Suspense還有用嗎
- 可跟蹤性。很多時候,我們使用Redux等框架並不僅僅是為了數據的共享,不如說大部分時候我們規劃全局狀態的結構時,就是有很多區塊根本不共享的。但是Redux提供的機制、提供的工具確實增加了可跟蹤性與可調試性(如時光機),這些在Suspense上的支持尚未可知
- 簡化的可測性。你可以設計FetcherFactory然後再根據場景來mock fetcher重新擁有可測性,但就我看來,做得到這程度的前端……嗯……並不多?
- 聲明式編程的堅持。原始的React設計中render是一個純聲明式的,由一個數據(props)驅動的純函數,我不知道有多少人領悟了這裡的「聲明式」的要義,但現在引入Suspense後我認為對聲明式是有破壞性的,render正在向命令式變化,這是我覺得比較悲哀的一點,當更多地人失去了對聲明式的執著之後,一個React應用會變得更難以維護。
從這些方面來看,我武斷地認為,在複雜的應用系統中,使用Suspense應該被限制。
從另一個角度說,如果只是為了當前Suspense表現出來的能力,那麼不用Suspense也不會造成任何的麻煩,比如我們現在是這麼做的:
function MovieDetailsInfo({movie}) {
return (
&
&
&{movie.title}&
&
&
}
const fetchMovie = props =&> fetchMovieDetails(props.id);
const MovieDetails = requireFetch(fetchMovie)(MovieDetails);
我就問你有個啥區別。所以我暫時沒有理解最初的那句「things that would take me hours to build before, I can now do in 30 minutes」是想要怎麼推算。
再追加一個缺點。因為Suspense把一個「有狀態的」及「有副作用」的過程引入到了一個原本一直強調是純函數的函數中,對於無法真正理解其核心的人來說,這會變成一個基於誤解被「模仿」成各種錯誤實踐的源頭。
比如能不能在render里dispatch一個action呢?能不能在render里調用callback prop通知上游呢?能不能在render里來一發window.location.href = xxx呢?你看你都能非同步載入數據了,我有什麼不能的呢?
於是,一個應用就這麼殘廢了,這是我最最害怕看到的結果。
再來說說優點吧。Suspense這個東西設計允許render中有多少個非同步操作我現在還不知道,但我希望並堅定地認為只會允許一個。
為什麼我這麼說呢?當只有一個的時候,如果你在render函數中希望發起多個非同步請求,你會怎麼做?答案很顯然,就是拆組件。
你看,僅僅因為這樣的一個小小的限制,你就會自覺得用一個非常靠譜的實踐(數據的粒度)去正確地拆解組件了。這是我很想要強調的,優秀的設計能讓人自覺得寫出優秀的代碼(上一段你不是這麼說的喂!)。
關於優秀的設計引導優秀的代碼,是我最近一直在深度和研究的一個話題。近期在制定前後端的API規範的時候,對這一點也感慨頗深。我選擇了一個看上去很特立獨行的規範,一個看上去需要很多封裝工作並沒有現成框架支持的規範,也就是為了在規範的引導下,大家能在設計系統的時候看到問題、提供良好正確的設計。
總結一下:
- 使用異常來實現Suspense我並不認同
- Suspense的設計我認為好用但要限制
- 如果你只是想要Suspense的非同步載入數據的能力,自己實現一個就可以開幹了
- 其實搞個HOC早就有提供這能力
- Suspense表現出來的對render函數的認知的破壞可能會造成潛在的React應用混亂和質量下降
- 好的設計往往要同時引導人去寫好的代碼
單說 Suspense 吧。
最近遇到了類似場景,思路和解決方案跟 Suspense 很類似。
- Render 時讀緩存,緩存存在則正常渲染
- 緩存不存在時,渲染一個佔位組件,同時創建一個 promise
- promise resolve 的時候,把之前的佔位組件用新的組件替換掉。
跟 Suspense 相比雖然山寨了點,但是用著還是不錯的。
為了解決的問題是,通過參數動態載入組件的時候,在組件載入完畢之前就可以把它當成一個普通組件使用,而不需要為父組件添加額外生命周期。
原來這才是React這個名字更好的解釋,之前對於React,是偏向開發者的角度來看的,也就是數據響應頁面,通過修改數據去驅動界面修改,而等到React16的出現,我覺得對React的理解的是偏向用戶體驗的,能讓界面快速響應用戶的操作。可能有點文不對題,但是Time Slice和Suspence都是手段,Responsive才是目的。
新特性的介紹其實直接去看官博就足夠了:
Sneak Peek: Beyond React 16 - React Blog?reactjs.org既然問的是『如何評價』,那當然是回答一些自己的看法吧。
我一直覺得,前端框架經過這麼多年的發展,軍備競賽已經進入下半場了,因為在頁面性能、開發體驗or效率上,之間已經很難有明顯的差異了(雖然較真的話還是有些區別,但不是那麼重要了)
那麼下一個競爭的熱點顯然就是用戶體驗,如果能讓開發者能在最短時間內快速寫出用戶體驗更佳的應用,那麼就很容易得到青睞。
React 從 v16 開始的一系列新特性(錯誤邊界、Fragment、Portal),大多數都是關注於如何幫助開發者最大程度地提升用戶體驗,這次的 Time Slice 和 Suspense 當然也不例外。尤其是 Suspense 可以很優雅地解決 Loading 態、佔位符以及避免閃屏的問題。
我們曾經的前端工程過於關注一些技術指標,比如大部分前端優化方法都是在告訴你怎麼讓資源體積更小、載入速度更快、首屏時間更短,而讓我們忽略了『用戶體驗』本身。
大部分前端工程對於頁面載入速度的優化已經比較完善了,很難獲得數量級上的優化了。更重要的是,頁面本身載入快慢個幾百毫秒真的很難感知出來,但有沒有做好載入態、有沒有優雅漂亮的內容展現方式(而不是唐突的閃出來),這些細節才是用戶真正能感知到的,也是我們未來更需要重視的,而 React v16 也正在幫助我們完成這些事情。
我在一篇文章中詳細描述了 React 核心成員 Dan 在演講中提到的 Time Slicing 和 Suspense。
編譯青春:React 的未來:Time Slicing 和 Suspense?zhuanlan.zhihu.com覺得這個功能可有可無呀。。。不是特別好,而且破壞了聲明。其實這個功能在 Provider 的概念來做就行了,不過還沒項目中使用過,所以不能說完全不好,只是覺得其實不需要官方侵入來做這個事情。還有就是不要讓它變得更重其實比較好,React 已經幫前端解決不少問題了,就目前非同步數據管理還未統一,可能官方也想提供一個比較好的實踐吧。(???ω???)搞來搞去,這層用後端的成熟經驗就好。
推薦閱讀:
※如何理解 Vue.JS 2016年的 github 星標( Star )數量增長超過 React ?
※TypeScript在React高階組件中的使用技巧
※koa 實現 react-view 原理
※如何看待文章<WHY REACT/REDUX IS AN INFERIOR PARADIGM>?
※使用 React.js 的漸進式 Web 應用程序:第 3 部分 - 離線支持和網路恢復能力
TAG:React |