自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

高端操作:把 React Scheduler 掏出來(lái)單獨(dú)用

開(kāi)發(fā) 前端
Scheduler 是 React 提供的底層調(diào)度器。但是這個(gè)調(diào)度器具體是如何用的,可能大部分人都不太清楚了,好在 React 把內(nèi)部的模塊封裝得都相對(duì)獨(dú)立,因此,我們可以想個(gè)辦法,單獨(dú)把他的 Scheduler 或者 Reconciler 單獨(dú)掏出來(lái)用。

我們知道,Scheduler 是 React 提供的底層調(diào)度器。但是這個(gè)調(diào)度器具體是如何用的,可能大部分人都不太清楚了,好在 React 把內(nèi)部的模塊封裝得都相對(duì)獨(dú)立,因此,我們可以想個(gè)辦法,單獨(dú)把他的 Scheduler 或者 Reconciler 單獨(dú)掏出來(lái)用。

一、怎么掏

在 React 的 github 倉(cāng)庫(kù)中,找到如下路徑的文件:./packages/scheduler/src。

這里就是 Scheduler 的全部代碼,如圖所示,我們可以在 forks 目錄中,找到 Scheduler.js,這就是我們的目標(biāo)文件,他引用了外部的幾個(gè)小模塊的內(nèi)容。

// packages/scheduler/src/forks/Scheduler.js
import type {PriorityLevel} from '../SchedulerPriorities';

import {
  enableSchedulerDebugging,
  enableProfiling,
  enableIsInputPending,
  enableIsInputPendingContinuous,
  frameYieldMs,
  continuousYieldMs,
  maxYieldMs,
  userBlockingPriorityTimeout,
  lowPriorityTimeout,
  normalPriorityTimeout,
} from '../SchedulerFeatureFlags';

import {push, pop, peek} from '../SchedulerMinHeap';

// TODO: Use symbols?
import {
  ImmediatePriority,
  UserBlockingPriority,
  NormalPriority,
  LowPriority,
  IdlePriority,
} from '../SchedulerPriorities';
import {
  markTaskRun,
  markTaskYield,
  markTaskCompleted,
  markTaskCanceled,
  markTaskErrored,
  markSchedulerSuspended,
  markSchedulerUnsuspended,
  markTaskStart,
  stopLoggingProfilingEvents,
  startLoggingProfilingEvents,
} from '../SchedulerProfiling';

export type Callback = boolean => ?Callback;

這里需要注意的是,從 github 上掏出來(lái)的代碼不是用 TS 寫(xiě)的,而是用 flow 寫(xiě)的,因此這里部分語(yǔ)法可能會(huì)報(bào)錯(cuò),需要我們要自己稍作調(diào)整才能直接使用,不過(guò)改動(dòng)不大。

SchedulerFeatureFlags.js 的代碼非常簡(jiǎn)單,就是定義了一些狀態(tài)來(lái)區(qū)分不同的執(zhí)行階段。

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 */

export const enableSchedulerDebugging = false;
export const enableIsInputPending = false;
export const enableProfiling = false;
export const enableIsInputPendingContinuous = false;
export const frameYieldMs = 5;
export const continuousYieldMs = 50;
export const maxYieldMs = 300;

export const userBlockingPriorityTimeout = 250;
export const normalPriorityTimeout = 5000;
export const lowPriorityTimeout = 10000;

SchedulerMinHeap.js 封裝了幾個(gè)小頂堆的操作方法,用于優(yōu)先級(jí)隊(duì)列的任務(wù)管理,因此常用的操作就是 pop、push、peek。

SchedulerPriorities.js 定義了幾個(gè)優(yōu)先級(jí)的常量。

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 */

export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;

// TODO: Use symbols?
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

SchedulerProfiling.js 是用來(lái)分析性能的,我們?cè)谡{(diào)試的時(shí)候可以用一下。一般來(lái)說(shuō)都會(huì)將其關(guān)掉。

直接把這些文件復(fù)制出來(lái),整理好,就能單獨(dú)使用了。我們可以看一下 Scheduler.js 返回了什么方法。

export {
  ImmediatePriority as unstable_ImmediatePriority,
  UserBlockingPriority as unstable_UserBlockingPriority,
  NormalPriority as unstable_NormalPriority,
  IdlePriority as unstable_IdlePriority,
  LowPriority as unstable_LowPriority,
  unstable_runWithPriority,
  unstable_next,
  unstable_scheduleCallback,
  unstable_cancelCallback,
  unstable_wrapCallback,
  unstable_getCurrentPriorityLevel,
  shouldYieldToHost as unstable_shouldYield,
  requestPaint as unstable_requestPaint,
  unstable_continueExecution,
  unstable_pauseExecution,
  unstable_getFirstCallbackNode,
  getCurrentTime as unstable_now,
  forceFrameRate as unstable_forceFrameRate,
};

我們可以在源碼中去明確這些方法的具體使用方式,然后根據(jù)你的需要選擇使用即可。

二、語(yǔ)法介紹

我們可以使用 unstable_scheduleCallback 來(lái)調(diào)度任務(wù),這個(gè)方法接收三個(gè)參數(shù)。

function unstable_scheduleCallback(
  priorityLevel: PriorityLevel,
  callback: Callback,
  options?: {delay: number},
)

priorityLevel 需要的參數(shù)我們?cè)谏厦嬉呀?jīng)定義好的,數(shù)字越小,優(yōu)先級(jí)越高。

callback 就是我們需要被調(diào)度的任務(wù)。

options 中,我們可以傳入 delay,來(lái)進(jìn)一步降低任務(wù)執(zhí)行的優(yōu)先級(jí),表示延遲任務(wù)。他會(huì)進(jìn)入到 timerQueue 隊(duì)列而無(wú)法直接執(zhí)行,只有在特定時(shí)機(jī)移入到了 taskQueue 中之后才會(huì)被執(zhí)行。

unstable_scheduleCallback 返回一個(gè) Task 對(duì)象,我們可以在源碼中看到這個(gè)對(duì)象大概長(zhǎng)這樣。

var newTask: Task = {
  id: taskIdCounter++,
  callback,
  priorityLevel,
  startTime,
  expirationTime,
  sortIndex: -1,
};

unstable_cancelCallback 可以取消正在調(diào)度的任務(wù),在源碼內(nèi)部?jī)?nèi)容,它通過(guò)重置 task.callback = null 來(lái)取消。

OK,了解了基本用法之后,我們就可以來(lái)使用它調(diào)度任務(wù)了。

三、使用

想同優(yōu)先級(jí)

想想如下代碼輸出順序如何?

unstable_scheduleCallback(NormalPriority, () => {
  console.log(1)
})

unstable_scheduleCallback(NormalPriority, () => {
  console.log(2)
})

unstable_scheduleCallback(NormalPriority, () => {
  console.log(3)
})

unstable_scheduleCallback(NormalPriority, () => {
  console.log(4)
})

// 輸出順序:1, 2, 3, 4

由于他們優(yōu)先級(jí)相同,所以會(huì)按照任務(wù)創(chuàng)建的先后順序來(lái)確定誰(shuí)的優(yōu)先級(jí)更高。因此,先創(chuàng)建的先執(zhí)行。

不同優(yōu)先級(jí)

現(xiàn)在我們調(diào)整一下優(yōu)先級(jí),思考一下代碼輸出順序如何。

unstable_scheduleCallback(LowPriority, () => {
  console.log(1)
})

unstable_scheduleCallback(NormalPriority, () => {
  console.log(2)
})

unstable_scheduleCallback(ImmediatePriority, () => {
  console.log(3)
})

unstable_scheduleCallback(NormalPriority, () => {
  console.log(4)
})
// 輸出結(jié)果:3,2,4,1

此時(shí)優(yōu)先級(jí)不同,則優(yōu)先級(jí)越高的先執(zhí)行。

任務(wù)是否超時(shí)

我們?cè)趧?chuàng)建任務(wù)時(shí),會(huì)給任務(wù)添加一個(gè) expirationTime 字段來(lái)表示任務(wù)執(zhí)行時(shí),是否超時(shí)。在回調(diào)函數(shù)中,可以接收一個(gè)參數(shù)來(lái)標(biāo)記超時(shí)狀態(tài)。

unstable_scheduleCallback(NormalPriority, (isTimeout) => {
  console.log(4)
  console.log(isTimeout)
})

他的判斷標(biāo)準(zhǔn)如下:

const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;

expirationTime 的計(jì)算規(guī)則如下:

var timeout;
switch (priorityLevel) {
  case ImmediatePriority:
    // Times out immediately
    timeout = -1;
    break;
  case UserBlockingPriority:
    // Eventually times out
    timeout = userBlockingPriorityTimeout;
    break;
  case IdlePriority:
    // Never times out
    timeout = maxSigned31BitInt;
    break;
  case LowPriority:
    // Eventually times out
    timeout = lowPriorityTimeout;
    break;
  case NormalPriority:
  default:
    // Eventually times out
    timeout = normalPriorityTimeout;
    break;
}

var expirationTime = startTime + timeout;

上面案例通常情況下會(huì)返回 false,但是我們可以在主線(xiàn)程中執(zhí)行一下耗時(shí)任務(wù),讓其無(wú)法在超時(shí)時(shí)間以?xún)?nèi)執(zhí)行。NormalPriority 優(yōu)先級(jí)的超時(shí)時(shí)間至少是 5000ms。

sunstable_scheduleCallback(NormalPriority, (isTimeout) => {
  console.log(4)
  console.log(isTimeout) // false
})

const currentTime = performance.now()
while(performance.now() - currentTime < 5000) {}
unstable_scheduleCallback(NormalPriority, (isTimeout) => {
  console.log(4)
  console.log(isTimeout) // true,執(zhí)行時(shí)已經(jīng)超時(shí)
})

const currentTime = performance.now()
while(performance.now() - currentTime < 5000) {}

再來(lái)看一個(gè)例子:

unstable_scheduleCallback(UserBlockingPriority, (isTimeout) => {
  console.log(2)
  console.log(isTimeout) // true
})

unstable_scheduleCallback(ImmediatePriority, (isTimeout) => {
  console.log(3)
  const currentTime = performance.now()
  while(performance.now() - currentTime < 100) {}
  console.log(isTimeout) // true
})

unstable_scheduleCallback(NormalPriority, (isTimeout) => {
  console.log(4)
  console.log(isTimeout) // false
})

const currentTime = performance.now()
while(performance.now() - currentTime < 200) {}

此時(shí)主線(xiàn)程卡住 200ms,因此 3 ImmediatePriority 超時(shí)。此時(shí) 3 執(zhí)行,又卡了 100ms,那么 2 UserBlockingPriority 對(duì)應(yīng) 250ms 延遲時(shí)間,此時(shí)也超時(shí)了。

任務(wù)中斷

此時(shí)我們要聲明一個(gè)任務(wù)來(lái)遍歷一個(gè)數(shù)組,數(shù)組中的每一項(xiàng)的執(zhí)行時(shí)間都比較長(zhǎng),聲明數(shù)組如下:

const tasks: any[] = [
  ["1", 3],
  ["2", 3],
  ["3", 5],
  ["4", 7],
  ["5", 9],
];

我們可以結(jié)合 unstable_shouldYield 來(lái)判斷當(dāng)前執(zhí)行時(shí)間是否過(guò)長(zhǎng),然后以中斷遍歷過(guò)程的方式,中斷任務(wù)的執(zhí)行。

function node_task() {
  console.log('開(kāi)始執(zhí)行任務(wù)')
  var task
  while(task = tasks.shift()) {
    var now = performance.now()
    // 卡住執(zhí)行
    while(performance.now() - now < task[1]) {}
    console.log(task[0], '小任務(wù)執(zhí)行完畢')
    if (unstable_shouldYield()) {
      console.log('執(zhí)行超過(guò)了 5ms,中斷執(zhí)行')
      return node_task
    }
  }
}

unstable_shouldYield 是超過(guò) 5ms 就需要中斷一次,此時(shí)我們發(fā)現(xiàn),任務(wù) 1 與 任務(wù) 2 加起來(lái)超過(guò)了 5ms,因此 2 執(zhí)行完之后,會(huì)中斷一次。,后面的每個(gè)任務(wù)都比較長(zhǎng),因此每個(gè)任務(wù)執(zhí)行完都會(huì)中斷一次,所以總共會(huì)中斷 4 次。

調(diào)度之后,我們看看打印結(jié)果:

unstable_scheduleCallback(NormalPriority, node_task);

完整的符合預(yù)期。

高優(yōu)先級(jí)插隊(duì)

我們只需要把上面的案例稍作調(diào)整,就能做到高優(yōu)先級(jí)插隊(duì)。在 node_task 的執(zhí)行過(guò)程中,我們利用 setTimeout 調(diào)度一個(gè)更高優(yōu)先級(jí)的任務(wù)。

const tasks: any[] = [
  ["1", 3],
  ["2", 3],
  ["3", 5],
  ["4", 7],
  ["5", 9],
];

function node_task() {
  console.log('--開(kāi)始執(zhí)行任務(wù)--')
  var task
  while(task = tasks.shift()) {
    var now = performance.now()
    // 卡住執(zhí)行
    while(performance.now() - now < task[1]) {}
    console.log(task[0], '小任務(wù)執(zhí)行完畢')
    if (unstable_shouldYield()) {
      console.log('執(zhí)行超過(guò)了 5ms,中斷執(zhí)行')
      return node_task
    }
  }
}

unstable_scheduleCallback(NormalPriority, node_task);
+ setTimeout(() => {
+   unstable_scheduleCallback(ImmediatePriority, () => {
+     console.log('我是高優(yōu)先級(jí)插隊(duì)')
+  });
+ }, 10)

執(zhí)行結(jié)果如下,插隊(duì)成功。

四、總結(jié)

我們可以利用這一套優(yōu)先級(jí)隊(duì)列的調(diào)度,解決實(shí)踐中的需求。例如,在開(kāi)發(fā)彈幕功能的時(shí)候,我們會(huì)想辦法優(yōu)先讓自己發(fā)的彈幕先彈出來(lái)?;蛘咴谙棿疤崾緯r(shí),優(yōu)先彈出錯(cuò)誤警告等。

責(zé)任編輯:姜華 來(lái)源: 這波能反殺
相關(guān)推薦

2022-12-06 08:30:06

SchedulerReact

2021-11-30 06:56:59

MySQL幻讀查詢(xún)

2020-05-26 08:32:56

Python代碼開(kāi)發(fā)

2022-02-15 08:49:29

繼承C++variant

2013-11-05 10:01:45

2018-04-04 14:44:08

2009-12-09 13:46:25

2021-08-09 17:13:39

數(shù)值運(yùn)算

2024-08-07 10:19:00

2021-04-12 06:04:30

React操作系統(tǒng)Reconciler

2021-04-09 18:01:03

前端ReactDOM

2023-11-29 09:29:48

Kuberneteskube

2015-10-29 09:36:31

高端編程語(yǔ)言

2023-03-03 15:37:32

GMP 模型goroutine

2021-01-29 08:22:03

調(diào)度器Yarn架構(gòu)

2015-10-19 09:17:08

2022-04-26 08:51:29

MySQLgroup by

2014-10-14 16:17:52

DCS存儲(chǔ)操作系統(tǒng)

2021-01-05 09:07:30

Django ORMF查詢(xún)Q查詢(xún)

2016-03-17 09:45:17

react雙向綁定插件
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)