1. 「React18新特性」深入淺出用戶(hù)體驗(yàn)—transition

        共 11237字,需瀏覽 23分鐘

         ·

        2021-12-25 10:01

        在 React 18 中,引進(jìn)了一個(gè)新的 API —— startTransition 還有二個(gè)新的 hooks —— useTransitionuseDeferredValue,本質(zhì)上它們離不開(kāi)一個(gè)概念 transition 。

        什么叫做 transition 英文翻譯為 ‘過(guò)渡’,那么這里的過(guò)渡指的就是在一次更新中,數(shù)據(jù)展現(xiàn)從無(wú)到有的過(guò)渡效果。用 ReactWg 中的一句話(huà)描述 startTransition 。

        在大屏幕視圖更新的時(shí),startTransition 能夠保持頁(yè)面有響應(yīng),這個(gè) api 能夠把 React 更新標(biāo)記成一個(gè)特殊的更新類(lèi)型 transitions ,在這種特殊的更新下,React 能夠保持視覺(jué)反饋和瀏覽器的正常響應(yīng)。

        單單從上述對(duì) startTransition 的描述,我們很難理解這個(gè)新的 api 到底解決什么問(wèn)題。不過(guò)不要緊,接下來(lái)讓我逐步分析這個(gè) api 到底做了什么,以及它的應(yīng)用場(chǎng)景。

        二 transition 使命

        1 transition 的誕生

        為什么會(huì)出現(xiàn) Transition 呢?Transition 本質(zhì)上解決了渲染并發(fā)的問(wèn)題,在 React 18 關(guān)于 startTransition 描述的時(shí)候,多次提到 ‘大屏幕’ 的情況,這里的大屏幕并不是單純指的是尺寸,而是一種數(shù)據(jù)量大,DOM 元素節(jié)點(diǎn)多的場(chǎng)景,比如數(shù)據(jù)可視化大屏情況,在這一場(chǎng)景下,一次更新帶來(lái)的變化可能是巨大的,所以頻繁的更新,執(zhí)行 js 事務(wù)頻繁調(diào)用,瀏覽器要執(zhí)行大量的渲染工作,所以給用戶(hù)感覺(jué)就是卡頓。

        Transition 本質(zhì)上是用于一些不是很急迫的更新上,在 React 18 之前,所有的更新任務(wù)都被視為急迫的任務(wù),在 React 18 誕生了 concurrent Mode 模式,在這個(gè)模式下,渲染是可以中斷,低優(yōu)先級(jí)任務(wù),可以讓高優(yōu)先級(jí)的任務(wù)先更新渲染。可以說(shuō) React 18 更青睞于良好的用戶(hù)體驗(yàn)。從 ?concurrent Modesusponse 再到 startTransition 無(wú)疑都是圍繞著更優(yōu)質(zhì)的用戶(hù)體驗(yàn)展開(kāi)。

        startTransition 依賴(lài)于 concurrent Mode 渲染并發(fā)模式。也就是說(shuō)在 React 18 中使用 startTransition ,那么要先開(kāi)啟并發(fā)模式,也就是需要通過(guò) createRoot 創(chuàng)建 Root 。我們先來(lái)看一下兩種模式下,創(chuàng)建 Root 區(qū)別。

        傳統(tǒng) legacy 模式

        import?ReactDOM?from?'react-dom'
        /*?通過(guò)?ReactDOM.render??*/
        ReactDOM.render(
        ????<App?/>,
        ????document.getElementById('app')
        )

        v18 concurrent Mode并發(fā)模式

        import?ReactDOM?from?'react-dom'
        /*?通過(guò)?createRoot?創(chuàng)建?root?*/
        const?root?=??ReactDOM.createRoot(document.getElementById('app'))
        /*?調(diào)用?root?的?render?方法?*/
        root.render(<App/>)

        上面說(shuō)了 startTransition 使用條件,接下來(lái)探討一下 startTransition 到底應(yīng)用于什么場(chǎng)景。前面說(shuō)了 React 18 確定了不同優(yōu)先級(jí)的更新任務(wù),為什么會(huì)有不同優(yōu)先級(jí)的任務(wù)。世界上本來(lái)沒(méi)有路,走的人多了就成了路,優(yōu)先級(jí)產(chǎn)生也是如此,React 世界里本來(lái)沒(méi)有優(yōu)先級(jí),場(chǎng)景多了就出現(xiàn)了優(yōu)先級(jí)。

        如果一次更新中,都是同樣的任務(wù),那么也就無(wú)任務(wù)優(yōu)先級(jí)可言,統(tǒng)一按批次處理任務(wù)就可以了,可現(xiàn)實(shí)恰好不是這樣子。舉一個(gè)很常見(jiàn)的場(chǎng)景:就是有一個(gè) input 表單。并且有一個(gè)大量數(shù)據(jù)的列表,通過(guò)表單輸入內(nèi)容,對(duì)列表數(shù)據(jù)進(jìn)行搜索,過(guò)濾。那么在這種情況下,就存在了多個(gè)并發(fā)的更新任務(wù)。分別為

        • 第一種:input 表單要實(shí)時(shí)獲取狀態(tài),所以是受控的,那么更新 input 的內(nèi)容,就要觸發(fā)更新任務(wù)。
        • 第二種:input 內(nèi)容改變,過(guò)濾列表,重新渲染列表也是一個(gè)任務(wù)。

        第一種類(lèi)型的更新,在輸入的時(shí)候,希望是的視覺(jué)上馬上呈現(xiàn)變化,如果輸入的時(shí)候,輸入的內(nèi)容延時(shí)顯示,會(huì)給用戶(hù)一種極差的視覺(jué)體驗(yàn)。第二種類(lèi)型的更新就是根據(jù)數(shù)據(jù)的內(nèi)容,去過(guò)濾列表中的數(shù)據(jù),渲染列表,這個(gè)種類(lèi)的更新,和上一種比起來(lái)優(yōu)先級(jí)就沒(méi)有那么高。那么如果 input 搜索過(guò)程中用戶(hù)更優(yōu)先希望的是輸入框的狀態(tài)改變,那么正常情況下,在 input 中綁定 onChange 事件用來(lái)觸發(fā)上述的兩種類(lèi)的更新。

        const?handleChange=(e)=>{
        ???/*?改變搜索條件?*/?
        ???setInputValue(e.target.value)
        ???/*?改變搜索過(guò)濾后列表狀態(tài)?*/
        ???setSearchQuery(e.target.value)
        }

        上述這種寫(xiě)法,那么 setInputValuesetSearchQuery 帶來(lái)的更新就是一個(gè)相同優(yōu)先級(jí)的更新。而前面說(shuō)道,輸入框狀態(tài)改變更新優(yōu)先級(jí)要大于列表的更新的優(yōu)先級(jí)。 ,這個(gè)時(shí)候我們的主角就登場(chǎng)了。用 startTransition 把兩種更新區(qū)別開(kāi)。

        const?handleChange=()=>{
        ????/*?高優(yōu)先級(jí)任務(wù)?——?改變搜索條件?*/
        ????setInputValue(e.target.value)
        ????/*?低優(yōu)先級(jí)任務(wù)?——?改變搜索過(guò)濾后列表狀態(tài)??*/
        ????startTransition(()=>{
        ????????setSearchQuery(e.target.value)
        ????})
        }
        • 如上通過(guò) startTransition 把不是特別迫切的更新任務(wù) setSearchQuery ?隔離出來(lái)。這樣在真實(shí)的情景效果如何呢?我們來(lái)測(cè)試一下。

        2 模擬場(chǎng)景

        接下來(lái)我們模擬一下上述場(chǎng)景。流程大致是這樣的:

        • 有一個(gè)搜索框和一個(gè) 10000 條數(shù)據(jù)的列表,列表中每一項(xiàng)有相同的文案。
        • input 改變要實(shí)時(shí)改變 input 的內(nèi)容(第一種更新),然后高亮列表里面的相同的搜索值(第二種更新)。
        • 用一個(gè)按鈕控制 常規(guī)模式 | transition 模式。
        /*??模擬數(shù)據(jù)??*/
        const?mockDataArray?=?new?Array(10000).fill(1)
        /*?高量顯示內(nèi)容?*/
        function?ShowText({?query?}){
        ???const?text?=?'asdfghjk'
        ???let?children
        ???if(text.indexOf(query)?>?0?){
        ???????/*?找到匹配的關(guān)鍵詞?*/
        ???????const?arr?=?text.split(query)
        ???????children?=?<div>{arr[0]}<span?style={{?color:'pink'?}}?>{query}span>{arr[1]}?div>
        ???}else{
        ??????children?=?<div>{text}div>
        ???}
        ???return?<div>{children}div>
        }
        /*?列表數(shù)據(jù)?*/
        function?List?({?query?}){
        ????console.log('List渲染')
        ????return?<div>
        ????????{
        ???????????mockDataArray.map((item,index)=><div?key={index}?>
        ??????????????<ShowText?query={query}?/>
        ???????????div>
        )
        ????????}
        ????div>
        }
        /*?memo?做優(yōu)化處理??*/
        const?NewList?=?memo(List)
        • List 組件渲染一萬(wàn)個(gè) ShowText 組件。在 ShowText 組件中會(huì)通過(guò)傳入的 query 實(shí)現(xiàn)動(dòng)態(tài)高亮展示。
        • 因?yàn)槊恳淮胃淖?query 都會(huì)讓 10000 個(gè)重新渲染更新,并且還要展示 query 的高亮內(nèi)容,所以滿(mǎn)足并發(fā)渲染的場(chǎng)景。

        接下來(lái)就是 App 組件編寫(xiě)。

        export?default?function?App(){
        ????const?[?value?,setInputValue?]?=?React.useState('')
        ????const?[?isTransition?,?setTransion?]?=?React.useState(false)
        ????const?[?query?,setSearchQuery??]?=?React.useState('')
        ????const?handleChange?=?(e)?=>?{
        ????????/*?高優(yōu)先級(jí)任務(wù)?——?改變搜索條件?*/
        ????????setInputValue(e.target.value)
        ????????if(isTransition){?/*?transition?模式?*/
        ????????????React.startTransition(()=>{
        ????????????????/*?低優(yōu)先級(jí)任務(wù)?——?改變搜索過(guò)濾后列表狀態(tài)??*/
        ????????????????setSearchQuery(e.target.value)
        ????????????})
        ????????}else{?/*?不加優(yōu)化,傳統(tǒng)模式?*/
        ????????????setSearchQuery(e.target.value)
        ????????}
        ????}
        ????return?<div>
        ????????<button?onClick={()=>setTransion(!isTransition)}?>{isTransition???'transition'?:?'normal'}?button>

        ????????<input?onChange={handleChange}
        ????????????placeholder="輸入搜索內(nèi)容"
        ????????????value={value}
        ????????/>

        ???????<NewList??query={query}?/>
        ????div>
        }

        我們看一下 App 做了哪些事情。

        • 首先通過(guò) handleChange 事件來(lái)處理 onchange 事件。
        • button按鈕用來(lái)切換 transition (設(shè)置優(yōu)先級(jí)) 和 normal (正常模式)。接下來(lái)就是見(jiàn)證神奇的時(shí)刻。

        常規(guī)模式下效果:


        • 可以清楚的看到在常規(guī)模式下,輸入內(nèi)容,內(nèi)容呈現(xiàn)都變的異常卡頓,給人一種極差的用戶(hù)體驗(yàn)。

        transtion 模式下效果:


        • 把大量并發(fā)任務(wù)通過(guò) startTransition 處理之后,可以清楚看到,input 會(huì)正常的呈現(xiàn),更新列表任務(wù)變得滯后,不過(guò)用戶(hù)體驗(yàn)大幅度提升,

        整體效果:


        • 來(lái)感受一些 startTransition 的魅力。

        總結(jié): 通過(guò)上面可以直觀的看到 startTransition 在處理過(guò)渡任務(wù),優(yōu)化用戶(hù)體驗(yàn)上起到了舉足輕重的作用。

        3 為什么不是 setTimeout

        上述的問(wèn)題能夠把 setSearchQuery 的更新包裝在 setTimeout 內(nèi)部呢,像如下這樣。

        const?handleChange=()=>{
        ????/*?高優(yōu)先級(jí)任務(wù)?——?改變搜索條件?*/
        ????setInputValue(e.target.value)
        ????/*?把?setSearchQuery?通過(guò)延時(shí)器包裹??*/
        ????setTimeout(()=>{
        ????????setSearchQuery(e.target.value)
        ????},0)
        }
        • 這里通過(guò) setTimeout ,把更新放在 setTimeout 內(nèi)部,那么我們都知道 setTimeout 是屬于延時(shí)器任務(wù),它不會(huì)阻塞瀏覽器的正常繪制,瀏覽器會(huì)在下次空閑時(shí)間之行 setTimeout 。那么效果如何呢?我們來(lái)看一下:
        4.gif
        • 如上可以看到,通過(guò) setTimeout 確實(shí)可以讓輸入狀態(tài)好一些,但是由于 setTimeout 本身也是一個(gè)宏任務(wù),而每一次觸發(fā) onchange 也是宏任務(wù),所以 setTimeout 還會(huì)影響頁(yè)面的交互體驗(yàn)。

        綜上所述,startTransition 相比 setTimeout 的優(yōu)勢(shì)和異同是:

        • 一方面:startTransition 的處理邏輯和 setTimeout 有一個(gè)很重要的區(qū)別,setTimeout 是異步延時(shí)執(zhí)行,而 startTransition 的回調(diào)函數(shù)是同步執(zhí)行的。在 startTransition 之中任何更新,都會(huì)標(biāo)記上 transition,React 將在更新的時(shí)候,判斷這個(gè)標(biāo)記來(lái)決定是否完成此次更新。所以 Transition 可以理解成比 setTimeout 更早的更新。但是同時(shí)要保證 ui 的正常響應(yīng),在性能好的設(shè)備上,transition 兩次更新的延遲會(huì)很小,但是在慢的設(shè)備上,延時(shí)會(huì)很大,但是不會(huì)影響 UI 的響應(yīng)。

        • 另一方面,就是通過(guò)上面例子,可以看到,對(duì)于渲染并發(fā)的場(chǎng)景下,setTimeout 仍然會(huì)使頁(yè)面卡頓。因?yàn)槌瑫r(shí)后,還會(huì)執(zhí)行 setTimeout 的任務(wù),它們與用戶(hù)交互同樣屬于宏任務(wù),所以仍然會(huì)阻止頁(yè)面的交互。那么 transition 就不同了,在 conCurrent mode 下,startTransition 是可以中斷渲染的 ,所以它不會(huì)讓頁(yè)面卡頓,React 讓這些任務(wù),在瀏覽器空閑時(shí)間執(zhí)行,所以上述輸入 input 內(nèi)容時(shí),startTransition 會(huì)優(yōu)先處理 input 值的更新,而之后才是列表的渲染。

        4 為什么不是節(jié)流防抖

        那么我們?cè)傧胍粋€(gè)問(wèn)題,為什么不是節(jié)流和防抖。首先節(jié)流和防抖能夠解決卡頓的問(wèn)題嗎?答案是一定的,在沒(méi)有 transition 這樣的 api 之前,就只能通過(guò)防抖節(jié)流來(lái)處理這件事,接下來(lái)用防抖處理一下。

        const?SetSearchQueryDebounce?=?useMemo(()=>?debounce((value)=>?setSearchQuery(value),1000)??,[])
        const?handleChange?=?(e)?=>?{
        ????setInputValue(e.target.value)
        ????/*?通過(guò)防抖處理后的 setSearchQuery 函數(shù)。??*/
        ????SetSearchQueryDebounce(e.target.value)
        }
        • 如上將 setSearchQuery 防抖處理。然后我們看一下效果。


        通過(guò)上面可以直觀感受到通過(guò)防抖處理后,基本上已經(jīng)不影響 input 輸入了。但是面臨一個(gè)問(wèn)題就是 list 視圖改變的延時(shí)時(shí)間變長(zhǎng)了。那么 transition 和節(jié)流防抖 本質(zhì)上的區(qū)別是:

        • 一方面,節(jié)流防抖 本質(zhì)上也是 setTimeout ,只不過(guò)控制了執(zhí)行的頻率,那么通過(guò)打印的內(nèi)容就能發(fā)現(xiàn),原理就是讓 render 次數(shù)減少了。而 transitions 和它相比,并沒(méi)有減少渲染的次數(shù)。

        • 另一方面,節(jié)流和防抖需要有效掌握 Delay Time 延時(shí)時(shí)間,如果時(shí)間過(guò)長(zhǎng),那么給人一種渲染滯后的感覺(jué),如果時(shí)間過(guò)短,那么就類(lèi)似于 setTimeout(fn,0) 還會(huì)造成前面的問(wèn)題。而 startTransition 就不需要考慮這么多。

        5 受到計(jì)算機(jī)性能影響

        transition 在處理慢的計(jì)算機(jī)上效果更加明顯,我們來(lái)看一下 Real world example

        注意看滑塊速度

        • 處理性能高,更快速的設(shè)備上。不使用 startTransition 。

        • 處理性能高,更快速的設(shè)備上。使用 startTransition。


        • 處理性能差,慢速的設(shè)備上,不使用 startTransition。


        • 處理性能差,慢速的設(shè)備上,使用 startTransition。


        三 transition 特性

        既然已經(jīng)講了 transition 的產(chǎn)生初衷,接下來(lái)看 transition 的功能介紹 。

        1 什么是過(guò)度任務(wù)。

        一般會(huì)把狀態(tài)更新分為兩類(lèi):

        • 第一類(lèi)緊急更新任務(wù)。比如一些用戶(hù)交互行為,按鍵,點(diǎn)擊,輸入等。
        • 第二類(lèi)就是過(guò)渡更新任務(wù)。比如 UI 從一個(gè)視圖過(guò)渡到另外一個(gè)視圖。

        2 什么是 startTransition

        上邊已經(jīng)用了 startTransition 開(kāi)啟過(guò)度任務(wù),對(duì)于 startTransition 的用法,相信很多同學(xué)已經(jīng)清楚了。

        startTransition(scope)
        • scope 是一個(gè)回調(diào)函數(shù),里面的更新任務(wù)都會(huì)被標(biāo)記成過(guò)渡更新任務(wù),過(guò)渡更新任務(wù)在渲染并發(fā)場(chǎng)景下,會(huì)被降級(jí)更新優(yōu)先級(jí),中斷更新。

        使用

        startTransition(()=>{
        ???/*?更新任務(wù)?*/
        ???setSearchQuery(value)
        })

        3 什么是 useTranstion

        上面介紹了 startTransition ,又講到了過(guò)渡任務(wù),本質(zhì)上過(guò)渡任務(wù)有一個(gè)過(guò)渡期,在這個(gè)期間當(dāng)前任務(wù)本質(zhì)上是被中斷的,那么在過(guò)渡期間,應(yīng)該如何處理呢,或者說(shuō)告訴用戶(hù)什么時(shí)候過(guò)渡任務(wù)處于 pending 狀態(tài),什么時(shí)候 pending 狀態(tài)完畢。

        為了解決這個(gè)問(wèn)題,React 提供了一個(gè)帶有 isPending 狀態(tài)的 hooks —— useTransition 。useTransition 執(zhí)行返回一個(gè)數(shù)組。數(shù)組有兩個(gè)狀態(tài)值:

        • 第一個(gè)是,當(dāng)處于過(guò)渡狀態(tài)的標(biāo)志——isPending。
        • 第二個(gè)是一個(gè)方法,可以理解為上述的 startTransition??梢园牙锩娴母氯蝿?wù)變成過(guò)渡任務(wù)。
        import?{?useTransition?}?from?'react'?

        /*?使用?*/
        const??[?isPending?,?startTransition?]?=?useTransition?()

        那么當(dāng)任務(wù)處于懸停狀態(tài)的時(shí)候,isPendingtrue,可以作為用戶(hù)等待的 UI 呈現(xiàn)。比如:

        {?isPending??&&???}

        useTranstion 實(shí)踐

        接下來(lái)我們做一個(gè) useTranstion 的實(shí)踐,還是復(fù)用上述 demo 。對(duì)上述 demo 改造。

        export?default?function?App(){
        ????const?[?value?,setInputValue?]?=?React.useState('')
        ????const?[?query?,setSearchQuery??]?=?React.useState('')
        ????const?[?isPending?,?startTransition?]?=?React.useTransition()
        ????const?handleChange?=?(e)?=>?{
        ????????setInputValue(e.target.value)
        ????????startTransition(()=>{
        ????????????setSearchQuery(e.target.value)
        ????????})
        ????}
        ????return??<div>
        ????{isPending?&&?<span>isTransitonspan>
        }
        ????<input?onChange={handleChange}
        ????????placeholder="輸入搜索內(nèi)容"
        ????????value={value}
        ????/>

        ???<NewList??query={query}?/>
        div>
        }
        • 如上用 useTransition , isPending 代表過(guò)渡狀態(tài),當(dāng)處于過(guò)渡狀態(tài)時(shí)候,顯示 isTransiton 提示。

        接下來(lái)看一下效果:





        可以看到能夠準(zhǔn)確捕獲到過(guò)渡期間的狀態(tài)。

        4 什么是 useDeferredValue

        如上場(chǎng)景我們發(fā)現(xiàn),本質(zhì)上 query 也是 value ,不過(guò) query 的更新要滯后于 value 的更新。那么 React 18 提供了 useDeferredValue 可以讓狀態(tài)滯后派生。useDeferredValue 的實(shí)現(xiàn)效果也類(lèi)似于 transtion,當(dāng)迫切的任務(wù)執(zhí)行后,再得到新的狀態(tài),而這個(gè)新的狀態(tài)就稱(chēng)之為 DeferredValue 。

        useDeferredValue 和上述 useTransition 本質(zhì)上有什么異同呢?

        相同點(diǎn):

        • useDeferredValue 本質(zhì)上和內(nèi)部實(shí)現(xiàn)與 useTransition ?一樣都是標(biāo)記成了過(guò)渡更新任務(wù)。

        不同點(diǎn):

        • useTransition 是把 startTransition 內(nèi)部的更新任務(wù)變成了過(guò)渡任務(wù)transtion,而 useDeferredValue 是把原值通過(guò)過(guò)渡任務(wù)得到新的值,這個(gè)值作為延時(shí)狀態(tài)。 一個(gè)是處理一段邏輯,另一個(gè)是生產(chǎn)一個(gè)新的狀態(tài)。
        • useDeferredValue 還有一個(gè)不同點(diǎn)就是這個(gè)任務(wù),本質(zhì)上在 useEffect 內(nèi)部執(zhí)行,而 useEffect 內(nèi)部邏輯是異步執(zhí)行的 ,所以它一定程度上更滯后于 useTransition。useDeferredValue = useEffect + transtion

        那么回到 demo 上來(lái),似乎 query 變成 DeferredValue 更適合現(xiàn)實(shí)情況,那么對(duì) demo 進(jìn)行修改。

        export?default?function?App(){
        ????const?[?value?,setInputValue?]?=?React.useState('')
        ????const?query?=?React.useDeferredValue(value)
        ????const?handleChange?=?(e)?=>?{
        ????????setInputValue(e.target.value)
        ????}
        ????return??<div>
        ?????<button>useDeferredValuebutton>

        ????<input?onChange={handleChange}
        ????????placeholder="輸入搜索內(nèi)容"
        ????????value={value}
        ????/>

        ???<NewList??query={query}?/>
        ???div>
        }
        • 如上可以看到 query 是 value 通過(guò) useDeferredValue 產(chǎn)生的。

        效果:

        7.gif

        四 原理

        接下來(lái)又到了原理環(huán)節(jié),從 startTransition 到 useTranstion 再到 useDeferredValue 原理本質(zhì)上很簡(jiǎn)單,

        1 startTransition

        首先看一下最基礎(chǔ)的 startTransition 是如何實(shí)現(xiàn)的。

        react/src/ReactStartTransition.js -> startTransition

        export?function?startTransition(scope)?{
        ??const?prevTransition?=?ReactCurrentBatchConfig.transition;
        ??/*?通過(guò)設(shè)置狀態(tài)?*/
        ??ReactCurrentBatchConfig.transition?=?1;
        ??try?{??
        ??????/*?執(zhí)行更新?*/
        ????scope();
        ??}?finally?{
        ????/*?恢復(fù)狀態(tài)?*/??
        ????ReactCurrentBatchConfig.transition?=?prevTransition;
        ??}
        }
        • startTransition 原理特別簡(jiǎn)單,有點(diǎn)像 React v17 中 batchUpdate 的批量處理邏輯。就是通過(guò)設(shè)置開(kāi)關(guān)的方式,而開(kāi)關(guān)就是 transition = 1 ,然后執(zhí)行更新,里面的更新任務(wù)都會(huì)獲得 transtion 標(biāo)志。

        • 接下來(lái)在 concurrent mode 模式下會(huì)單獨(dú)處理 transtion 類(lèi)型的更新。

        其原理圖如下所示。

        9.jpg

        2 useTranstion

        接下來(lái)看一下 useTranstion 的內(nèi)部實(shí)現(xiàn)。

        react-reconciler/src/ReactFiberHooks.new.js -> useTranstion

        function?mountTransition(){
        ????const?[isPending,?setPending]?=?mountState(false);
        ????const?start?=?(callback)=>{
        ????????setPending(true);
        ????????const?prevTransition?=?ReactCurrentBatchConfig.transition;
        ????????ReactCurrentBatchConfig.transition?=?1;
        ????????try?{
        ????????????setPending(false);
        ????????????callback();
        ????????}?finally?{
        ????????????ReactCurrentBatchConfig.transition?=?prevTransition;
        ????????}
        ????}
        ?????return?[isPending,?start];
        }

        這段代碼不是源碼,我把源碼里面的內(nèi)容進(jìn)行組合,壓縮。

        • 從上面可以看到,useTranstion 本質(zhì)上就是 useState + ?startTransition 。
        • 通過(guò) useState 來(lái)改變 pending 狀態(tài)。在 mountTransition 執(zhí)行過(guò)程中,會(huì)觸發(fā)兩次 setPending ,一次在 transition = 1 之前,一次在之后。一次會(huì)正常更新 setPending(true) ,一次會(huì)作為 transition 過(guò)渡任務(wù)更新 setPending(false); ,所以能夠精準(zhǔn)捕獲到過(guò)渡時(shí)間。

        其原理圖如下所示。

        10.jpg

        3 useDeferredValue

        最后,讓我們看一下 useDeferredValue 的內(nèi)部實(shí)現(xiàn)原理。

        react-reconciler/src/ReactFiberHooks.new.js -> useTranstion

        function?updateDeferredValue(value){
        ??const?[prevValue,?setValue]?=?updateState(value);
        ??updateEffect(()?=>?{
        ????const?prevTransition?=?ReactCurrentBatchConfig.transition;
        ????ReactCurrentBatchConfig.transition?=?1;
        ????try?{
        ??????setValue(value);
        ????}?finally?{
        ??????ReactCurrentBatchConfig.transition?=?prevTransition;
        ????}
        ??},?[value]);
        ??return?prevValue;
        }

        useDeferredValue 處理流程是這樣的。

        • 從上面可以看到 useDeferredValue 本質(zhì)上是 useDeferredValue = useState + useEffect + transition
        • 通過(guò)傳入 useDeferredValue 的 value 值,useDeferredValue 通過(guò) useState 保存狀態(tài)。
        • 然后在 useEffect 中通過(guò) transition 模式來(lái)更新 value 。這樣保證了 DeferredValue 滯后于 state 的更新,并且滿(mǎn)足 transition ?過(guò)渡更新原則。

        其原理圖如下所示。

        11.jpg

        四 總結(jié)

        本章節(jié)講到的知識(shí)點(diǎn)如下:

        • Transition 產(chǎn)生初衷,解決了什么問(wèn)題。
        • startTransition 的用法和原理。
        • useTranstion 的用法和原理。
        • useDeferredValue 的用法和原理。

        感興趣的同學(xué)可以是一下

        參考文檔

        • New feature: startTransition

        • Real world example: adding startTransition for slow renders

        瀏覽 105
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 大香蕉超碰 | 大香蕉在线观看视频 | 丁香五月综合激情啪啪啪 | 久久6| 伊人久久大香线蕉 |