最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

react?Scheduler?實(shí)現(xiàn)示例教程

 更新時(shí)間:2022年09月17日 10:21:30   作者:fangjiapeng  
這篇文章主要為大家介紹了react?Scheduler?實(shí)現(xiàn)示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

最近在看react源碼,react構(gòu)建fiber樹這一塊邏輯還比較好理解,但是一旦涉及到任務(wù)調(diào)度相關(guān)的邏輯,看起來是一頭霧水。在參考了一些資料和react scheduler源碼后,我決定來實(shí)現(xiàn)一個(gè)簡單版的scheduler,相信跟著本文的思路實(shí)現(xiàn)一遍,就可以理解為什么react需要有scheduler這個(gè)東西來調(diào)度任務(wù)。

簡單的背景知識(shí):

我們知道現(xiàn)在大部分設(shè)備的幀率都是60fps,也就是說瀏覽器每16.7ms會(huì)繪制一次。如果頁面上有一些動(dòng)畫,那么16.7s繪制一次,看起來是比較流暢的。

簡單的css動(dòng)畫

先來寫一個(gè)簡單的css動(dòng)畫:一個(gè)普通的div左右滑動(dòng)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #block {
            width: 50px;
            height: 50px;
            margin: 0 0;
            background-color: #ddd;
            animation: move 5s linear infinite;
            position: absolute;
        }
        @keyframes move {
            0% {
                left: 0;
            }
            25% {
                left: 100px;
            }
            50% {
                left: 200px;
            }
            75% {
                left: 100px;
            }
            100% {
                left: 0;
            }
        }
    </style>
</head>
<body>
    <div id="block"></div>
</body>
</html>

使用谷歌瀏覽器的性能錄制面板可以看到:

在主線程上,一幀的時(shí)間是16.7ms,我們放大看看一幀時(shí)間里面,瀏覽器做了什么:

完成一次繪制需要執(zhí)行Schedule Style Recalculation, Recalculate Style, Layout, Pre-Paint, Paint, Composite Layers。這里我們不細(xì)究在每個(gè)階段瀏覽器做了什么,只需要關(guān)注這個(gè)渲染是在主線程上進(jìn)行,由CPU完成的就行了。通常每16.7ms瀏覽器會(huì)繪制一次,但是如果本輪事件循環(huán)有任務(wù)在執(zhí)行,那么需要等任務(wù)執(zhí)行完再進(jìn)行繪制。如果任務(wù)耗時(shí)過長,繪制次數(shù)就會(huì)變少,也就是所謂“掉幀”。因?yàn)槲覀儸F(xiàn)在頁面非常簡單,沒有js任務(wù),所以瀏覽器每16.7ms繪制一次,動(dòng)畫看起來很流暢。

現(xiàn)在我們來加上一個(gè)按鈕,點(diǎn)擊之后會(huì)創(chuàng)建5個(gè)任務(wù),每個(gè)任務(wù)耗時(shí)20ms,并且馬上執(zhí)行。

<body>
    <button id="btn">click me</button>
</body>

綁定事件:

const works = [];
const btn = document.getElementById('btn');
btn.onclick = function () {
    for (let i = 0; i < 5; i++) {
        works.push(macroTask)
    }
    flushWork();
}
function macroTask(){
    const start = new Date().getTime();
    while (new Date().getTime() - start < 20) {}
}
function flushWork(){
    while(works.length){
        const work = works.shift();
        work.call(null);
    }
}

點(diǎn)擊按鈕會(huì)發(fā)現(xiàn),正在滑動(dòng)的div卡頓了一下,通過下圖可以看到,瀏覽器直到5個(gè)宏任務(wù)完成后才會(huì)執(zhí)行渲染,在這段時(shí)間里面,頁面不能更新,也不能響應(yīng)用戶操作。

etTimeout來實(shí)現(xiàn)

如果點(diǎn)擊按鈕要執(zhí)行成千上百個(gè)任務(wù),那么瀏覽器會(huì)卡死很長一段時(shí)間,這顯然是不能接受的。最簡單的改造方法是執(zhí)行一個(gè)任務(wù)后,把后續(xù)的任務(wù)處理放到下一個(gè)事件循環(huán),讓瀏覽器可以在本輪事件循環(huán)執(zhí)行繪制。精通瀏覽器原理的你肯定知道可以利用setTimeout來實(shí)現(xiàn):

const works = [];
const btn = document.getElementById('btn');
btn.onclick = function () {
    for (let i = 0; i < 50; i++) {
        works.push(macroTask)
    }
    flushWork();
}
function macroTask(){
    const start = Date.now();
    while (Date.now() - start < 20) {}
}
function flushWork(){
    workLoop();
}
function workLoop(){
    const work = works.shift();
    if(work){
        work.call(null);
        // 只執(zhí)行一個(gè)任務(wù),后面的下個(gè)事件循環(huán)再處理
        setTimeout(workLoop, 0);
    }
}

打開控制臺(tái)分析一下:

現(xiàn)在可以看到,現(xiàn)在每個(gè)宏任務(wù)都沒有連在一起,它們在不同的事件循環(huán)里執(zhí)行。每個(gè)任務(wù)完成后,瀏覽器都會(huì)執(zhí)行一次繪制,就算要執(zhí)行的任務(wù)非常多,動(dòng)畫也不會(huì)卡住不動(dòng)了。

但是,仔細(xì)觀察一下,后面的宏任務(wù)間隔好像都比較大,放大看間隔大概是4ms左右。我們現(xiàn)在一個(gè)任務(wù)的執(zhí)行時(shí)間是20ms,超過了16.7ms,事實(shí)上頁面已經(jīng)有一點(diǎn)卡頓了。主線程資源這么緊張,每個(gè)事件循環(huán)居然還要浪費(fèi)4ms,這肯定是不能接受的。很多人應(yīng)該都聽說過setTimeout的最小延時(shí)限制,大概意思就是雖然你是setTimeout零秒,實(shí)際上嵌套多層之后,至少要過4ms左右,宏任務(wù)才會(huì)進(jìn)入到任務(wù)隊(duì)列。

循環(huán)處理

setTimeout不能用了,有其他替代方案嗎?答案是有的,我們可以使用MessageChannel來把任務(wù)放到宏任務(wù)隊(duì)列。 MessageChannel的用法就不詳細(xì)介紹了,簡單地說,就是利用這個(gè)api,我們可以監(jiān)聽一個(gè)message事件,當(dāng)事件觸發(fā)的時(shí)候,事件處理函數(shù)這個(gè)任務(wù)會(huì)加入到宏任務(wù)隊(duì)列。對應(yīng)我們的例子,我們就可以綁定onmessage的時(shí)候執(zhí)行workLoop, 在workLoop里面只執(zhí)行一個(gè)任務(wù),如果還有任務(wù)沒有執(zhí)行,那就postMessage,在下一個(gè)事件循環(huán)繼續(xù)處理。

const channel = new MessageChannel();
const port2 = channel.port2;
const port1 = channel.port1;
port1.onmessage = workLoop;
const works = [];
const btn = document.getElementById('btn');
btn.onclick = function () {
    for (let i = 0; i < 50; i++) {
        works.push(macroTask)
    }
    flushWork();
}
function macroTask(){
    const start = Date.now();
    while (Date.now() - start < 20) {}
}
function flushWork(){
    workLoop();
}
function workLoop(){
    const work = works.shift();
    if(work){
        work.call(null);
        port2.postMessage(null);
    }
}

重新執(zhí)行后再分析一下,宏任務(wù)之間基本沒有間隔了:

目前我們的最小任務(wù)單元的執(zhí)行時(shí)間是20ms。因?yàn)槌^了16.7ms會(huì)導(dǎo)致頁面變卡頓,所以實(shí)際上我們應(yīng)該確保單個(gè)任務(wù)不能超過16.7ms。假設(shè)經(jīng)過合理的設(shè)計(jì),我們的最小任務(wù)單元執(zhí)行時(shí)間不會(huì)超過2ms(這里隨機(jī)設(shè)置成1ms或2ms)。然后再來看看點(diǎn)擊按鈕后執(zhí)行1000個(gè)任務(wù)會(huì)怎么樣。

const channel = new MessageChannel();
const port2 = channel.port2;
const port1 = channel.port1;
port1.onmessage = workLoop;
const works = [];
const btn = document.getElementById('btn');
btn.onclick = function () {
    for (let i = 0; i < 1000; i++) {
        works.push(macroTask)
    }
    flushWork();
}
function macroTask(){
    const time = [1, 2]; 
    const zeroOrOne = Math.round(Math.random());
    const start = Date.now();
    while (Date.now() - start < time[zeroOrOne]) {}
}
function flushWork(){
    workLoop();
}
function workLoop(){
    const work = works.shift();
    if(work){
        work.call(null);
        port2.postMessage(null);
    }
}

分析運(yùn)行結(jié)果,可以看到現(xiàn)在瀏覽器繪制的幀率還是沒有60fps,我們的任務(wù)占據(jù)主線程時(shí)間太長了。所以我們需要一種機(jī)制,使得在一幀的時(shí)間內(nèi)盡可能執(zhí)行多個(gè)任務(wù),而且留有充足的時(shí)間給瀏覽器繪制頁面和響應(yīng)用戶交互。

最終我們的設(shè)計(jì)方案是:在一個(gè)事件循環(huán)里面,我們只占用主線程5ms, 超過5ms就把主線程控制權(quán)交還給瀏覽器,在下一個(gè)事件循環(huán)處理任務(wù)。

具體思路

聲明一個(gè)全局隊(duì)列taskQueue存放任務(wù);

聲明一個(gè)全局變量startTime表示任務(wù)調(diào)度的開始時(shí)間, 當(dāng)接受到onmessage事件時(shí),獲取當(dāng)前時(shí)間賦值給startTime,然后開始調(diào)度任務(wù);

調(diào)度任務(wù):從taskQueue隊(duì)列中取出一個(gè)任務(wù),獲取當(dāng)前時(shí)間currentTime, 計(jì)算currentTime - startTime,如果大于或等于5ms,說明調(diào)度任務(wù)時(shí)長已經(jīng)達(dá)到5ms了,break出循環(huán),如果隊(duì)列里還有任務(wù),postMessage交出主線程控制權(quán),等下個(gè)事件循環(huán)再調(diào)度任務(wù)。

瀏覽器繪制完頁面,響應(yīng)用戶交互后,在下一個(gè)事件循環(huán)再次調(diào)度任務(wù),重新計(jì)算currentTime,startTime,此時(shí)它們的差值一定不會(huì)超過5ms, 取出一個(gè)任務(wù)執(zhí)行,然后更新currentTime。再次進(jìn)入while循環(huán),判斷currentTime - startTime是否大于5ms, 大于5ms就交出控制權(quán),否則繼續(xù)執(zhí)行下一個(gè)任務(wù)。

改造后的代碼:

const channel = new MessageChannel();
const port2 = channel.port2;
const port1 = channel.port1;
port1.onmessage = performWorkUntilDeadline;
const taskQueue = [];
let startTime = -1;
const frameYieldMs = 5; // 任務(wù)的連續(xù)執(zhí)行時(shí)間不能超過5ms
let currentTask = null; // 用來保存當(dāng)前的任務(wù)
btn.onclick = function () {
    for (let i = 0; i < 1000; i++) {
        taskQueue.push(macroTask)
    }
    // 在下個(gè)事件循環(huán)開始調(diào)度任務(wù)
    port2.postMessage(null);
}
function performWorkUntilDeadline() {
    startTime = performance.now(); // 更新開始時(shí)間
    let hasMoreWork = true;
    try {
        hasMoreWork = flushWork();
    } finally {
        currentTask = null;
        if(hasMoreWork) {
            port2.postMessage(null);
        }
    }
}
function flushWork(){
    return workLoop();
}
function workLoop() {
    // 這里用currentTask全局變量來保存當(dāng)前任務(wù)看起來似乎有點(diǎn)丑。
    // 其實(shí)是為了后續(xù)實(shí)現(xiàn)任務(wù)優(yōu)先級(jí)和任務(wù)插隊(duì)功能,先不管,就這么寫。
    currentTask = taskQueue[0];
    while(currentTask) {
        if(shouldYieldToHost()) {
            break;
        }
        currentTask.call(null);
        taskQueue.shift(); // 執(zhí)行完的任務(wù)從隊(duì)列中刪除
        currentTask = taskQueue[0]; // 繼續(xù)拿下一個(gè)任務(wù)
    }
    if(currentTask) {
        // 還有任務(wù)需要在下個(gè)事件循環(huán)處理
        return true;
    }
}
function shouldYieldToHost() {
    // 是否應(yīng)該掛起任務(wù)
    const currentTime = performance.now();
    if(currentTime - startTime < frameYieldMs) {
        return false;
    }
    return true;
}
function macroTask(){
    const time = [1, 2]; 
    const zeroOrOne = Math.round(Math.random());
    const start = performance.now();
    while (performance.now() - start < time[zeroOrOne]) {}
}

好了我們再看看運(yùn)行結(jié)果:瀏覽器的幀率現(xiàn)在已經(jīng)可以保持在60fps了,效果已經(jīng)很不錯(cuò)了。但是目前我們的任務(wù)隊(duì)列只是一個(gè)普通的先進(jìn)先出隊(duì)列,并沒有實(shí)現(xiàn)優(yōu)先級(jí)和任務(wù)插隊(duì)功能。下一篇文章我們將繼續(xù)跟著react的實(shí)現(xiàn)思路,用最小堆來實(shí)現(xiàn)優(yōu)先隊(duì)列。

以上就是react Scheduler 實(shí)現(xiàn)示例教程的詳細(xì)內(nèi)容,更多關(guān)于react Scheduler 教程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺談React之狀態(tài)(State)

    淺談React之狀態(tài)(State)

    這篇文章主要介紹了淺談React之狀態(tài)(State),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • ReactQuery系列之?dāng)?shù)據(jù)轉(zhuǎn)換示例詳解

    ReactQuery系列之?dāng)?shù)據(jù)轉(zhuǎn)換示例詳解

    這篇文章主要為大家介紹了ReactQuery系列之?dāng)?shù)據(jù)轉(zhuǎn)換示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • React事件機(jī)制源碼解析

    React事件機(jī)制源碼解析

    這篇文章主要介紹了React事件機(jī)制源碼解析的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用React框架,感興趣的朋友可以了解下
    2021-04-04
  • React Native使用百度Echarts顯示圖表的示例代碼

    React Native使用百度Echarts顯示圖表的示例代碼

    本篇文章主要介紹了React Native使用百度Echarts顯示圖表的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • React函數(shù)組件傳參的實(shí)現(xiàn)

    React函數(shù)組件傳參的實(shí)現(xiàn)

    React函數(shù)組件通過接受props實(shí)現(xiàn)組件間的數(shù)據(jù)傳遞,通過組件標(biāo)簽的屬性向子組件傳遞數(shù)據(jù),并在子組件中通過參數(shù)接收,還可以使用ES6的解構(gòu)賦值,函數(shù)也能作為props傳遞,以實(shí)現(xiàn)父子組件間的交互和通信,下面就來具體了解一下
    2024-09-09
  • React Js 微信禁止復(fù)制鏈接分享禁止隱藏右上角菜單功能

    React Js 微信禁止復(fù)制鏈接分享禁止隱藏右上角菜單功能

    這篇文章主要介紹了React Js 微信禁止復(fù)制鏈接,分享,禁止隱藏右上角菜單的解決代碼,需要的朋友可以參考下
    2017-05-05
  • react vue背景掛載機(jī)器問題

    react vue背景掛載機(jī)器問題

    這篇文章主要介紹了react vue背景掛載機(jī)器問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • react配合antd組件實(shí)現(xiàn)的管理系統(tǒng)示例代碼

    react配合antd組件實(shí)現(xiàn)的管理系統(tǒng)示例代碼

    這篇文章主要介紹了react配合antd組件實(shí)現(xiàn)的管理系統(tǒng)示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04
  • react滾動(dòng)加載useInfiniteScroll?詳解

    react滾動(dòng)加載useInfiniteScroll?詳解

    使用useInfiniteScroll?hook可以處理檢測用戶何時(shí)滾動(dòng)到頁面底部的邏輯,并觸發(fā)回調(diào)函數(shù)以加載更多數(shù)據(jù),它還提供了一種簡單的方法來管理加載和錯(cuò)誤消息的狀態(tài),今天通過實(shí)例代碼介紹下react滾動(dòng)加載useInfiniteScroll?相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧
    2023-09-09
  • React router動(dòng)態(tài)加載組件之適配器模式的應(yīng)用詳解

    React router動(dòng)態(tài)加載組件之適配器模式的應(yīng)用詳解

    這篇文章主要介紹了React router動(dòng)態(tài)加載組件之適配器模式的應(yīng)用 ,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-09-09

最新評論

蕲春县| 深圳市| 五华县| 克山县| 麻栗坡县| 台北市| 泰顺县| 汝州市| 宣城市| 禹城市| 台安县| 禄劝| 绩溪县| 鄂尔多斯市| 盐山县| 岱山县| 大厂| 斗六市| 高尔夫| 德钦县| 金门县| 辽阳县| 独山县| 进贤县| 三原县| 贞丰县| 马鞍山市| 汽车| 那坡县| 许昌县| 大渡口区| 禄丰县| 勃利县| 和硕县| 花垣县| 阳原县| 宁阳县| 灵石县| 长乐市| 大埔县| 建昌县|