React18新特性介紹&&升級指南

react歷次版本迭代主要想解決的是兩類導(dǎo)致網(wǎng)頁卡頓的問題卒煞,分別是cpu密集型任務(wù)和io密集型任務(wù)導(dǎo)致的卡頓問題企蹭,react18提出的并發(fā)特性(Concurrent Rendering)就是為了解決上述問題。

Concurrent Rendering

什么是concurrent

簡單體驗一下

concurrent不算是個新鮮概念,react很早之前就開始為其鋪路竖配,早在v16/v17就引入了fiber架構(gòu)和實驗性的concurrent Mode,開啟后整個應(yīng)用會開啟并發(fā)更新模式里逆,但這將帶來較大的breaking changes进胯。因此react18提出了Concurrent Rendering的概念,即沒有并發(fā)模式运悲,只有并發(fā)特性龄减,也就是說并發(fā)特性只是個可選項。默認(rèn)情況下整個應(yīng)用仍使用同步更新(legacy模式)班眯,在使用了并發(fā)特性后相關(guān)的更新再開啟并發(fā)更新希停,不用的話就沒有breaking changes烁巫。

concurrent帶來的變動可以概括為以下兩點:

時間分片

該模式下當(dāng)更新任務(wù)的render過程無法在瀏覽器的一幀內(nèi)完成時,會被分為多個task進(jìn)行可中斷的更新宠能,以此來保證瀏覽器每一幀都有空余時間進(jìn)行繪制亚隙,可以說時間分片是concurrent的實現(xiàn)基礎(chǔ)。

更新優(yōu)先級

該模式下更新任務(wù)會帶有優(yōu)先級违崇,低優(yōu)先級任務(wù)的執(zhí)行將讓位于高優(yōu)先級任務(wù)阿弃。

這句話有兩層含義,后面會有具體示例說明

  • 同一上下文中的高優(yōu)先級任務(wù)將優(yōu)先執(zhí)行

  • 不同上下文中的高優(yōu)先級任務(wù)將打斷正在執(zhí)行的低優(yōu)先級任務(wù)

什么是優(yōu)先級

legacy模式下沒有優(yōu)先級的概念羞延,因此所有任務(wù)都是同步執(zhí)行渣淳。而開啟并發(fā)特性后一切行為的基礎(chǔ)就是任務(wù)的優(yōu)先級。

在react應(yīng)用中我們可能在不同上下文中觸發(fā)setState伴箩,如點擊/輸入事件入愧,異步接口回調(diào),react18中也可能在concurrentAPI中觸發(fā)等等嗤谚。在react18中不同上下文中觸發(fā)的setState的優(yōu)先級是不一樣的棺蛛。react使用lane模型來描述優(yōu)先級,該模型使用31位二進(jìn)制來表示優(yōu)先級, 位數(shù)越泄健(值越信陨蕖)則優(yōu)先級越高。

以下是項目中最常見的幾類任務(wù)的優(yōu)先級

// 離散事件優(yōu)先級椅野,例如:點擊事件终畅,input輸入,focus等觸發(fā)的更新任務(wù)鳄橘,優(yōu)先級最高
export const DiscreteEventPriority: EventPriority = SyncLane;
// 連續(xù)事件優(yōu)先級声离,例如:滾動事件,拖動事件等
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// 默認(rèn)事件優(yōu)先級瘫怜,例如:異步接口回調(diào)中觸發(fā)的更新任務(wù)
export const DefaultEventPriority: EventPriority = DefaultLane;

渲染模式對比

結(jié)合performance對比下legacy與conurrent這兩種渲染模式

Legacy Mode

所謂legacy模式术徊,即傳統(tǒng)的react渲染模式,我們使用reactDOM.render創(chuàng)建的react應(yīng)用都是使用這種模式鲸湃,下面以一個demo為例分析赠涮。

渲染模式對比示例demo 在demo中用一個定時器延遲1000ms模擬接口請求,另一個定時器延遲1040ms模擬觸發(fā)一次點擊事件暗挑,可以明顯看到列表渲染和點擊事件的更新結(jié)果先后展示在視圖中笋除,分析:

image.png

可以看到列表請求觸發(fā)的更新和點擊事件觸發(fā)的更新先后進(jìn)行render,而列表更新的整個過程處于一個宏任務(wù)中且耗時200多ms炸裆,瀏覽器每16ms刷新一次垃它,因此導(dǎo)致render的過程中瀏覽器的每一幀都沒有時間繪制,反應(yīng)到真實場景中將使得用戶感到點擊操作卡頓。

總結(jié):legacy模式下的所有更新都是同步調(diào)度国拇,沒有優(yōu)先級之分洛史。

legacy下的更新調(diào)度流程
image.png

Concurrent Rendering

將上述demo開啟并發(fā)特性觀察效果,可以看到雖然點擊事件在時序上是后觸發(fā)的酱吝,但其更新結(jié)果卻優(yōu)先提交到了視圖也殖。分析:

image.png

注意到點擊事件觸發(fā)時已經(jīng)處于接口數(shù)據(jù)的render過程中,隨后點擊事件觸發(fā)的更新打斷了正在進(jìn)行的render而優(yōu)先執(zhí)行务热,提交到視圖后繼續(xù)進(jìn)行接口數(shù)據(jù)的render忆嗜。

總結(jié):

  • 開啟并發(fā)特性后更新任務(wù)將帶有優(yōu)先級,click事件的更新優(yōu)先級高于接口請求的更新優(yōu)先級崎岂,因而前者會打斷后者的render過程優(yōu)先執(zhí)行捆毫。

  • 當(dāng)更新的render流程過于耗時而超過瀏覽器一幀的時間時,更新任務(wù)將被分割為多個task進(jìn)行可中斷的更新冲甘,每個task的執(zhí)行時間不超過16ms(time slice)冻璃。這使得瀏覽器的每一幀中有空余時間進(jìn)行繪制,點擊事件的更新可以優(yōu)先呈現(xiàn)到視圖中损合。

  • 渲染階段(commit)是不可被打斷的。

并發(fā)模式帶來的優(yōu)勢是顯然易見的娘纷,他使得瀏覽器在任何情況下都有空余時間繪制嫁审,使得在不同性能的設(shè)備上緊急任務(wù)都能優(yōu)先render并提交到視圖。

concurrent下的更新調(diào)度流程圖
image.png

如何開啟Concurrent Rendering

react18提供了新的根結(jié)點創(chuàng)建方式:ReactDOM.createRoot()赖晶。使用此API創(chuàng)建的react應(yīng)用將啟用react18全部新特性律适。

import React from 'react';
// 注意這里ReactDOM是從client引入
import ReactDOM from 'react-dom/client';
import App from './contest';
ReactDOM.createRoot(document.getElementById('root')).render(<App />);

出于兼容性考慮,傳統(tǒng)的ReactDOM.renderAPI也會繼續(xù)保留遏插,使用ReactDOM.render創(chuàng)建的react18應(yīng)用的表現(xiàn)與react17完全一致捂贿。

image.png

Concurrent Render API

下面是react18新引入的用于開啟并發(fā)特性的API,只有用到這些API時才會開啟并發(fā)更新胳嘲。

startTransition

這是react18新引入的一個API厂僧,它允許我們以一個過渡優(yōu)先級TransitionLane)來調(diào)度一次更新×伺#可以稱這類更新為過渡任務(wù)颜屠。過渡任務(wù)擁有較低的優(yōu)先級,它帶來的影響可以從以下兩方面分析:

1.過渡任務(wù)的執(zhí)行過程將開啟時間分片

開啟時間分片后鹰祸,當(dāng)任務(wù)耗時過長時可以保證每一幀都能空出時間交給瀏覽器繪制甫窟,使得試圖不卡頓。

Time Slice示例demo

2.過渡任務(wù)的由于優(yōu)先級較低蛙婴,因此將讓位于其他高優(yōu)先級任務(wù)粗井。

高優(yōu)先級任務(wù)優(yōu)先執(zhí)行示例demo

以下稱setA(20000)為任務(wù)A,setB(1)為任務(wù)B

對于任務(wù)A,當(dāng)我們不用startTransition調(diào)度時浇衬,可以明顯看出a b以及列表是同時展示出來的懒构,這是由于effect中的兩次更新由于優(yōu)先級一致因此被合并更新,同步執(zhí)行径玖。當(dāng)使用startTransition調(diào)度時明顯看到b優(yōu)先變?yōu)?痴脾,這是由于任務(wù)A優(yōu)先級低于任務(wù)B,因此優(yōu)先執(zhí)行任務(wù)B梳星。即同一上下文中高優(yōu)先級任務(wù)將優(yōu)先執(zhí)行赞赖。

與setTimeout的區(qū)別

上述demo看起來似乎用setTimeout也能達(dá)到類似效果,事實上此API與setTimeout最重要的區(qū)別是處于transtions狀態(tài)的任務(wù)是可以中斷渲染的冤灾,是可以被高優(yōu)先級任務(wù)打斷的前域。對于渲染并發(fā)的場景下,setTimeout 仍然會使頁面卡頓韵吨,因為超時后匿垄,setTimeout 的任務(wù)還是會執(zhí)行且不可被打斷,仍然會阻塞頁面交互归粉。

將demo改造下椿疗,startTransition改為setTimeout,并在200ms后模擬觸發(fā)一次點擊事件(任務(wù)C)糠悼。由于點擊事件處于setTimeout中因此在任務(wù)隊列中它會排在任務(wù)A之后届榄,而列表的渲染時間明顯多于200ms,因此當(dāng)任務(wù)C執(zhí)行時一定還處于任務(wù)A的render過程中倔喂。而我們可以看到任務(wù)C的更新結(jié)果最后展示的铝条,這也印證了setTimeout中的更新任務(wù)一旦開始就是不可被打斷的。

最后將任務(wù)A還原為startTransition調(diào)度席噩,可以看到任務(wù)B班缰,任務(wù)C先后提交,任務(wù)A最后提交悼枢。我們知道點擊事件的優(yōu)先級高于過渡優(yōu)先級埠忘,因此任務(wù)C可以打斷任務(wù)A的render過程優(yōu)先執(zhí)行,這其實就是典型的高優(yōu)先級任務(wù)打斷低優(yōu)先級任務(wù)的執(zhí)行馒索。

與節(jié)流防抖的區(qū)別

節(jié)流防抖解決的是也是頻繁觸發(fā)渲染的問題给梅,但是還是會存在一些問題。比如100ms防抖双揪,當(dāng)列表渲染非扯穑快時,遠(yuǎn)遠(yuǎn)小于100ms渔期,但是卻需要等待到100ms后才會開始執(zhí)行更新运吓。而節(jié)流則無法解決更新耗時過長的問題渴邦。比如列表渲染需要耗時1s,那么在這1s內(nèi)用戶依舊無法進(jìn)行交互拘哨,其實與setTimeout的問題是類似的谋梭,而trasntions任務(wù)在過渡期間理論上是可以多次被高優(yōu)先級任務(wù)打斷的。

useTransition

startTransition可以調(diào)度一個過渡任務(wù)倦青,過渡任務(wù)有一個過渡期瓮床,可以認(rèn)為過渡任務(wù)的更新在被提交到視圖之前都屬于過渡期,而用戶無法感知當(dāng)前是否處于過渡期产镐。為了解決這個問題隘庄,React 提供了一個帶有 isPending 狀態(tài)的 hook:useTransition 。useTransition 執(zhí)行后返回一個數(shù)組癣亚,數(shù)組有兩個狀態(tài)值:

  • 當(dāng)處于過渡狀態(tài)的標(biāo)志—isPending丑掺。

  • startTransition,可以把里面的更新任務(wù)變成過渡任務(wù)述雾,等同于與上述的startTransitionAPI街州。

import { useTransition } from 'react' 
const  [ isPending , startTransition ] = useTransition ()

那么當(dāng)任務(wù)處于過渡狀態(tài)的時,isPending 為 true玻孟,可以作為用戶等待的 UI 呈現(xiàn)唆缴。比如:

{ isPending && <Spinner/> }

useTransition示例demo

useDefferedValue

useDeferredValue 的實現(xiàn)效果也類似于startTransition。

const [a, setA] = useState(0);
const deferredA = useDeferredValue(a);

useDeferredValue實質(zhì)上是基于原始state生產(chǎn)一個新的state(DeferredValue)黍翎,當(dāng)對原始state進(jìn)行setState時琐谤,DeferredValue的值會通過過渡任務(wù)得到,因此視圖中使用DeferredValue就會得到和startTransitionAPI一樣的效果玩敏,事實上這兩個API相當(dāng)于從兩個角度實現(xiàn)過渡任務(wù),本質(zhì)上是一樣的质礼。

useDeferredValue示例demo

其他變更

Automatic Batching

legacy模式下旺聚,除合成事件&生命周期外,在其他的事件回調(diào)中(異步方法眶蕉,原生事件等)的多次setState不會批量處理砰粹,即每次setState都會render一次。

legacy模式下的batchUpdate示例demo

每次setState后我們可以通過ref拿到最新的dom屬性造挽,在legacy模式下可以使用ReactDom.unstable_batchedUpdates強(qiáng)制批量更新碱璃,而在react18應(yīng)用中任何事件回調(diào)中的多次setState都會合并處理。

Automatic Batching示例demo

這是由于新版batchedUpdate的實現(xiàn)基于更新優(yōu)先級饭入,只要更新的優(yōu)先級一致那么更新將合并嵌器。

flushSync

特殊情況需要立即獲取更新結(jié)果時可以使用react18新提供的flushSync。

transitions與Suspense配合解決io瓶頸(不穩(wěn)定)

Suspense 是 React 提供的一種異步處理機(jī)制谐丢,在v16/v17中爽航,Suspense主要是配合React.lazy進(jìn)行組件層面的code spliting蚓让,而未來react希望逐步將Suspense用于所有的異步操作場景,目前已有相關(guān)API/庫進(jìn)行支持讥珍。

Suspense處理異步操作示例demo

demo中幾乎看不到異步代碼历极,完全用同步的思維獲取接口數(shù)據(jù)且不會用帶async/await這種語法糖。我們認(rèn)為數(shù)據(jù)是已經(jīng)存在的衷佃,我們做的只是讀數(shù)據(jù)而非拉數(shù)據(jù)趟卸。

上述demo的執(zhí)行流程如下:

  1. 首次調(diào)用userResource.read,會創(chuàng)建一個promise(即fetchUser的返回值)氏义。

  2. 由于是同步調(diào)用因此取不到數(shù)據(jù)锄列,此時userResource中會將這個promise throw出去

  3. React內(nèi)部會catch這個promise(handleEfrror)觅赊,離User組件最近的祖先Suspense組件渲染fallback

renderRootConcurrent

// renderRootConcurrent
do {
    try {
      workLoopConcurrent();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  1. promsie resolve或reject時重新觸發(fā)一次調(diào)度右蕊,此時再調(diào)用userResource.read會返回resolve/reject的結(jié)果(即fetchUser請求的數(shù)據(jù)),使用該數(shù)據(jù)繼續(xù)render

這里關(guān)鍵是userResource的實現(xiàn)吮螺,目前react有一個專門提供createReouceAPI的庫react-cache饶囚,但目前還處于實驗階段無法用于生產(chǎn)環(huán)境,下面簡單分析下實現(xiàn)原理鸠补。

const cache = {};
export function createResource(fetch) {
  const resource = {
    read: params => {
      // 這里臨時用id做個緩存 react-cache內(nèi)部有一套單獨的緩存清理算法
      if (!cache[params]) {
        const promise = fetch(params);
        let suspender = promise.then(
          r => {
            cache[params].status = 'resolved';
            cache[params].result = r;
          },
          e => {
            cache[params].status = 'rejected';
            cache[params].result = e;
          }
        );
        cache[params] = {
          promise: suspender,
          status: 'pending',
          result: null,
        };
        throw suspender;
      } else {
        if (cache[params].status === 'resolved') {
          return cache[params].result;
        }
        throw cache[params].promise;
      }
    },
  };
  return resource;
}

react18新增的concurrentAPI可以配合suspense使用萝风,當(dāng)startTranstion調(diào)度的更新任務(wù)觸發(fā)任意一個suspense組件掛起時,將導(dǎo)致當(dāng)前組件進(jìn)入pending狀態(tài)紫岩,此時只要關(guān)聯(lián)了suspense的變更就會被‘暫凸娑瑁’提交,直到在內(nèi)存中構(gòu)建完成后才會被會提交泉蝌。此特性一般用于接口返回較快且有l(wèi)oading的頁面歇万,可以在避免閃爍問題的同時,使得在視圖切換前仍然可以保持響應(yīng)勋陪。

github交互示例

將上述demo改造下

transitions+Suspense處理異步示例demo

目前suspense處理異步場景相關(guān)的庫尚不穩(wěn)定贪磺,猜測此特性后面可能由路由庫集成并暴露相關(guān)api給用戶。

移除inUnmount警告

image.png

日常開發(fā)中經(jīng)常碰到這個警告诅愚,它的本意是避免由于未及時清理effect hook中的訂閱而導(dǎo)致的內(nèi)存泄漏問題

useEffect(() => {
  function handleChange() {
    setState(store.getState());
  }
  store.subscribe(handleChange);
  return () => store.unsubscribe(handleChange);
}, []);

但日常開發(fā)中更多的警告場景是在已卸載的組件中進(jìn)行setState

async function handleSubmit() {
  setLoading(true);
  // 在我們等待時組件可能會卸載
  await post('/some-api');
  setLoading(false);
}

實際上這里并沒有實際的內(nèi)存泄漏寒锚,promise在resolve之后就會被回收。對于這種警告我們一般的實踐是手動判斷isUnmount违孝,但這實際上只是抑制了警告刹前,并沒有解決實質(zhì)問題且會增加代碼復(fù)雜度,因此是沒有必要的雌桑。react此次更新旨在剔除業(yè)務(wù)代碼中所有的isUnmount判斷喇喉。

組件卸載后setState會不會有其他副作用?

react觸發(fā)的任何更新最終都通過scheduleUpdateOnFiber進(jìn)行調(diào)度校坑,當(dāng)觸發(fā)更新所在組件已卸載時不繼續(xù)進(jìn)行調(diào)度流程轧飞,因此不會產(chǎn)生其他副作用衅鹿。

 // scheduleUpdateOnFiber

 // 當(dāng)內(nèi)部獲取不到根fiber節(jié)點時就不再繼續(xù)調(diào)度更新
 const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) {
    return null;
  }

markUpdateLaneFromFiberToRoot

function markUpdateLaneFromFiberToRoot(
){
 let node = sourceFiber;
 // fiber.return代表父節(jié)點,若當(dāng)前fiber對應(yīng)dom已卸載則fiber.return為null
 let parent = sourceFiber.return;
  while (parent !== null) {
  // 方法內(nèi)部會向上遍歷到根fiber節(jié)點
  }
  // 當(dāng)遍歷結(jié)果不是根fiber時會返回null
  if (node.tag === HostRoot) {
    const root: FiberRoot = node.stateNode;
    return root;
  } else {
    return null;
  }
}

允許組件返回undefined

React 17 中如果組件在 render 中返回了 undefined过咬,React 會在運行時拋錯大渤。

用于三方庫的API

以下API一般用于三方庫的開發(fā),通常不會用于實際業(yè)務(wù)開發(fā)當(dāng)中掸绞。

useInsertionEffect

這個Hook執(zhí)行時機(jī)在 DOM 生成之后泵三,useLayoutEffect執(zhí)行之前,此時無法訪問DOM節(jié)點的引用衔掸。一般用于解決 CSS-in-JS 庫在渲染中動態(tài)注入樣式的性能問題烫幕。

useSyncExternalStore

此API一般用于第三方狀態(tài)管理庫如redux/mobx,它們在控制狀態(tài)時可能并非直接使用react的 state敞映,而是自己在外部維護(hù)了一個store對象较曼,脫離了react的管理,此時若使用concurrentAPI則可能出現(xiàn)兼容性問題振愿,useExternalStore就是為了解決這種問題捷犹,基本實現(xiàn)原理是將render過程強(qiáng)制變?yōu)橥降牟豢芍袛嗟母隆?/p>

SSR

更多信息可見Upgrading to React 18 on the serverNew Suspense SSR Architecture in React 18

升級指南

官方升級指南

收益點

理論上任何由cpu密集型任務(wù)導(dǎo)致的卡頓問題都可以考慮是否可用并發(fā)特性優(yōu)化冕末。

兼容性

只要不用concurrentAPI那么表現(xiàn)將與舊版本一致萍歉。

redux/mobx兼容性

正常用沒問題(同步更新),但不能使用concurrentAPI調(diào)度store的更新操作档桃。

startTransition調(diào)度mobx更新示例demo

更新開始后枪孩,有 10 個 ShowText 節(jié)點需要render, 每個節(jié)點render時需要耗時 20ms 以上藻肄,這就導(dǎo)致每個 ShowText render結(jié)束以后都需要中斷讓出主線程蔑舞。在協(xié)調(diào)中斷時,修改 store 狀態(tài)嘹屯,后續(xù)的 ShowText 節(jié)點在恢復(fù) render 時攻询,會使用修改以后的 store 狀態(tài)導(dǎo)致最后出現(xiàn)狀態(tài)不一致的情況,因此在實際業(yè)務(wù)中極端情況下可能會出問題抚垄。

這是由于demo中脫離react state而在外部單獨維護(hù)數(shù)據(jù)源,而concurrent是react內(nèi)部狀態(tài)的處理機(jī)制谋逻,因此外部數(shù)據(jù)是無法處理更新中斷的問題(內(nèi)容撕裂問題)呆馁,redux中表現(xiàn)也是如此,不過最新的react-redux8.0.0中已經(jīng)使用useSyncExternalStoreAPI解決了此問題毁兆。

多次render帶來的影響

不安全生命周期

在高優(yōu)先級打斷低優(yōu)先級的case中浙滤,低優(yōu)先級任務(wù)事實上render了兩次,而

componentWillReceiveProps气堕、componentWillMount纺腊、componentWillUpdate 這幾個生命周期鉤子都是在render時觸發(fā)的畔咧,方法內(nèi)部都可以修改state,當(dāng)組件重復(fù)render時揖膜,不正當(dāng)的操作會引來額外的副作用誓沸,因此react將這幾個生命周期方法定義為 unsafe_xxxxx,在并發(fā)模式下可能有問題壹粟,目前項目中沒有用到這幾個鉤子拜隧。

饑餓問題

低優(yōu)先級任務(wù)的render過程多次被高優(yōu)先級任務(wù)打斷而得不到執(zhí)行的現(xiàn)象稱為饑餓問題。react通過過期時間來解決饑餓問題趁仙,不同優(yōu)先級對應(yīng)不同的過期時間洪添。當(dāng)?shù)蛢?yōu)先級任務(wù)一直未執(zhí)行而超過時期時間時該任務(wù)會被視為過期任務(wù),其優(yōu)先級會被提升為同步優(yōu)先級雀费,會立即執(zhí)行干奢。

饑餓問題示例demo

重復(fù)render問題

由于這種case只有在使用concurrentAPI時才有可能出現(xiàn),因此在開啟concurrent時需確認(rèn)組件中是否有在effect之外處理的特殊邏輯盏袄,即組件每次render都會執(zhí)行的邏輯忿峻。

高優(yōu)先級打斷低優(yōu)先級任務(wù)導(dǎo)致組件重復(fù)render示例demo

關(guān)于batchUpdates

需要排除項目中是否存在強(qiáng)行在兩次setState中立即取值的case,react18中此場景需要結(jié)合flushSync使用

不支持IE11

Concurrent模式下的任務(wù)調(diào)度流程

最后簡單分析下concurrentMode下的任務(wù)更新調(diào)度流程貌矿。
concurrentMode下的任務(wù)更新可以概括為異步可中斷的更新炭菌,這種基于更新任務(wù)的優(yōu)先級來統(tǒng)籌調(diào)度的模式的架構(gòu)基礎(chǔ)是fiber tree,它使得render過程中可以中斷逛漫。

function workLoopConcurrent() {
  // 當(dāng)wip構(gòu)造完成或時間切片用盡時停止工作
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

workLoopConcurrent 都會判斷本次協(xié)調(diào)對應(yīng)的優(yōu)先級和上一次時間片到期中斷的協(xié)調(diào)優(yōu)先級是否一樣黑低。如果一樣,說明沒有更高優(yōu)先級的更新產(chǎn)生酌毡,可以繼續(xù)上次未完成的協(xié)調(diào)毡泻;如果不一樣,說明有更高優(yōu)先級的更新進(jìn)來诲锹,此時要清空之前已開始的協(xié)調(diào)過程状囱,從根節(jié)點開始重新協(xié)調(diào)。等高優(yōu)先級更新處理完成以后旭蠕,再次從根節(jié)點開始處理低優(yōu)先級更新停团。

而調(diào)度的核心邏輯則主要來源于schduler模塊,schduler是一個獨立于react的專門用于任務(wù)調(diào)度的庫掏熬。react應(yīng)用每產(chǎn)生一個更新任務(wù)都會通過schduler模塊暴露的API(scheduleCallback)來注冊一個更新任務(wù)佑稠,此邏輯主要在ensureRootIsScheduled中完成:

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  // 得到當(dāng)前正在調(diào)度的fiber節(jié)點
  const existingCallbackNode = root.callbackNode;
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  // 獲得當(dāng)前根fiber節(jié)點下最高優(yōu)先級的lane
  const newCallbackPriority = returnNextLanesPriority();
  if (nextLanes === NoLanes) {
    return;
  }
  if (existingCallbackNode !== null) {
    // 當(dāng)本次調(diào)度的優(yōu)先級與正在調(diào)度的優(yōu)先級一致時則不繼續(xù)調(diào)度 auto batch
    const existingCallbackPriority = root.callbackPriority;
    if (existingCallbackPriority === newCallbackPriority) {
      return;
    }
    // 若不一致,說明本次的調(diào)度優(yōu)先級一定高于正在調(diào)度的優(yōu)先級旗芬,取消當(dāng)前的調(diào)度
    cancelCallback(existingCallbackNode);
  }
  // 注冊調(diào)度任務(wù)
  let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority) {
    // 同步優(yōu)先級 進(jìn)行同步調(diào)度 將在本輪事件循環(huán)同步執(zhí)行
    newCallbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    // 非同步優(yōu)先級舌胶,使用schduler進(jìn)行異步調(diào)度
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }
  // 更新標(biāo)記
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

schduler內(nèi)部維護(hù)一個隊列(taskQueue)來管理所有被調(diào)度的任務(wù),通過messageChannel來實現(xiàn)異步調(diào)度疮丛,此過程在unstable_scheduleCallback中完成幔嫂。每次調(diào)度都會從taskQueue中取出最高優(yōu)先級的任務(wù)執(zhí)行辆它,執(zhí)行過程中可能因為切片用盡或任務(wù)隊列已清空而中斷,再次回到隊列消費的邏輯履恩。此過程在workLoop中完成锰茉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市似袁,隨后出現(xiàn)的幾起案子洞辣,更是在濱河造成了極大的恐慌,老刑警劉巖昙衅,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扬霜,死亡現(xiàn)場離奇詭異,居然都是意外死亡而涉,警方通過查閱死者的電腦和手機(jī)著瓶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啼县,“玉大人材原,你說我怎么就攤上這事〖揪欤” “怎么了余蟹?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長子刮。 經(jīng)常有香客問我威酒,道長,這世上最難降的妖魔是什么挺峡? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任葵孤,我火速辦了婚禮,結(jié)果婚禮上橱赠,老公的妹妹穿的比我還像新娘尤仍。我一直安慰自己,他們只是感情好狭姨,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布宰啦。 她就那樣靜靜地躺著,像睡著了一般饼拍。 火紅的嫁衣襯著肌膚如雪赡模。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天惕耕,我揣著相機(jī)與錄音纺裁,去河邊找鬼诫肠。 笑死司澎,一個胖子當(dāng)著我的面吹牛欺缘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挤安,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼谚殊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛤铜?” 一聲冷哼從身側(cè)響起嫩絮,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎围肥,沒想到半個月后剿干,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡穆刻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年置尔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氢伟。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡榜轿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朵锣,到底是詐尸還是另有隱情谬盐,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布诚些,位于F島的核電站飞傀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泣刹。R本人自食惡果不足惜助析,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望椅您。 院中可真熱鬧外冀,春花似錦、人聲如沸掀泳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽员舵。三九已至脑沿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間马僻,已是汗流浹背庄拇。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人措近。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓溶弟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞭郑。 傳聞我的和親對象是個殘疾皇子辜御,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容