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ā)特性只是個可選項。默認情況下整個應(yīng)用仍使用同步更新(legacy模式),在使用了并發(fā)特性后相關(guān)的更新再開啟并發(fā)更新,不用的話就沒有breaking changes。

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

時間分片

該模式下當(dāng)更新任務(wù)的render過程無法在瀏覽器的一幀內(nèi)完成時,會被分為多個task進行可中斷的更新,以此來保證瀏覽器每一幀都有空余時間進行繪制,可以說時間分片是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位二進制來表示優(yōu)先級, 位數(shù)越?。ㄖ翟叫。﹦t優(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;
// 默認事件優(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ā)的更新先后進行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ā)的更新打斷了正在進行的render而優(yōu)先執(zhí)行,提交到視圖后繼續(xù)進行接口數(shù)據(jù)的render。

總結(jié):

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

  • 當(dāng)更新的render流程過于耗時而超過瀏覽器一幀的時間時,更新任務(wù)將被分割為多個task進行可中斷的更新,每個task的執(zhí)行時間不超過16ms(time slice)。這使得瀏覽器的每一幀中有空余時間進行繪制,點擊事件的更新可以優(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也能達到類似效果,事實上此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)列表渲染非常快時,遠遠小于100ms,但是卻需要等待到100ms后才會開始執(zhí)行更新。而節(jié)流則無法解決更新耗時過長的問題。比如列表渲染需要耗時1s,那么在這1s內(nèi)用戶依舊無法進行交互,其實與setTimeout的問題是類似的,而trasntions任務(wù)在過渡期間理論上是可以多次被高優(yōu)先級任務(wù)打斷的。

useTransition

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

  • 當(dāng)處于過渡狀態(tài)的標志—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進行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強制批量更新,而在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 提供的一種異步處理機制,在v16/v17中,Suspense主要是配合React.lazy進行組件層面的code spliting,而未來react希望逐步將Suspense用于所有的異步操作場景,目前已有相關(guān)API/庫進行支持。

Suspense處理異步操作示例demo

demo中幾乎看不到異步代碼,完全用同步的思維獲取接口數(shù)據(jù)且不會用帶async/await這種語法糖。我們認為數(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)前組件進入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ā)中更多的警告場景是在已卸載的組件中進行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進行調(diào)度,當(dāng)觸發(fā)更新所在組件已卸載時不繼續(xù)進行調(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í)行時機在 DOM 生成之后,useLayoutEffect執(zhí)行之前,此時無法訪問DOM節(jié)點的引用。一般用于解決 CSS-in-JS 庫在渲染中動態(tài)注入樣式的性能問題。

useSyncExternalStore

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

SSR

更多信息可見Upgrading to React 18 on the server、New 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而在外部單獨維護數(shù)據(jù)源,而concurrent是react內(nèi)部狀態(tài)的處理機制,因此外部數(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時需確認組件中是否有在effect之外處理的特殊邏輯,即組件每次render都會執(zhí)行的邏輯。

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

關(guān)于batchUpdates

需要排除項目中是否存在強行在兩次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)先級的更新進來,此時要清空之前已開始的協(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)先級 進行同步調(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進行異步調(diào)度
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }
  // 更新標記
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

schduler內(nèi)部維護一個隊列(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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,991評論 2 374

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