<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React中使用多線程—Web Worke

          共 16863字,需瀏覽 34分鐘

           ·

          2024-07-15 08:50

          ?

          和你的愛的人和愛你的人一起去見證美好

          ?
              

          大廠技術(shù)  高級(jí)前端  Node進(jìn)階

          點(diǎn)擊上方 程序員成長指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群

          前言

          作為一個(gè)前端開發(fā),如果你還停留在每天CRUD,還停留在切圖/畫圖,還停留在和后端同學(xué)對(duì)某個(gè)API設(shè)計(jì)的是否合理而大打出手時(shí),是時(shí)候停下來了。我們要變強(qiáng),我們需要對(duì)我們經(jīng)手的項(xiàng)目進(jìn)行一番改造和優(yōu)化。這才是我們能夠變強(qiáng)的方式。而不是,沉浸在無休止的爭吵和埋怨中。

          眾所周知,Javascript是一種「單線程語言」。因此,如果我們執(zhí)行任何耗時(shí)任務(wù),它將阻塞UI交互。用戶需要等待任務(wù)完成才能執(zhí)行其他操作,這會(huì)給用戶體驗(yàn)帶來不好的影響。

          其實(shí),針對(duì)此類問題,我們有很多解決方案,

          • 例如將耗時(shí)任務(wù)分割成多個(gè)短任務(wù),并讓其在多個(gè)渲染幀內(nèi)執(zhí)行,給UI交互(也就是UI渲染)留有時(shí)間,
          • 也可以通過回調(diào)的方式,在UI交互觸發(fā)后,在進(jìn)行耗時(shí)任務(wù)的操作。
          • 亦或者我們可以指定一個(gè)「優(yōu)先隊(duì)列」,當(dāng)高優(yōu)先級(jí)任務(wù)被執(zhí)行時(shí),低優(yōu)先級(jí)任務(wù)(耗時(shí)任務(wù))被降級(jí)處理(冷處理),直到高優(yōu)先級(jí)任務(wù)被執(zhí)行后再執(zhí)行剩余低優(yōu)先級(jí)任務(wù)。(這其實(shí)就是React并發(fā)的核心要點(diǎn))
          • ...等等

          上述列舉了很多解決方式,他們都有一個(gè)共同特點(diǎn) - 由于JS單線程屬性,它們只是將一些耗時(shí)任務(wù)從一個(gè)渲染幀分割或者延后到多個(gè)渲染幀內(nèi)。本質(zhì)上還是單線程的處理方式。

          而,今天我們就介紹一種利用「多線程(Web Worker)處理React中的耗時(shí)操作」。我們之前也在前面講過Web Worker的相關(guān)內(nèi)容。

          今天我們就詳細(xì)的介紹如何在前端項(xiàng)目中使用Web Worker用于處理耗時(shí)任務(wù),然后將長任務(wù)利用多線程的分割出主線程,然后給主線程留足時(shí)間去回應(yīng)更緊急的用戶操作,優(yōu)化用戶操作。

          好了,天不早了,干點(diǎn)正事哇。

          我們能所學(xué)到的知識(shí)點(diǎn)

          ?
          1. Web Workers
          2. React 的并發(fā)模式
          3. React 中使用Web Worker
          4. useWorker
          5. Web Worker的注意點(diǎn)
          ?

          1. Web Workers

          雖然,在之前的文章中介紹過Web Worker,但是為了最大限度的兼容大家的學(xué)習(xí)情況,還是打算簡單介紹一些。

          如上圖所示,JS中存在三中Worker,按照實(shí)現(xiàn)可以分為三類。

          1. Web Worker
          2. Shared Web Worker
          3. Service Worker

          而我們今天的主角-Web Worker是我們最常見的。

          ?

          Web Worker是在后臺(tái)運(yùn)行的腳本,不會(huì)影響用戶界面,因?yàn)樗?strong>「單獨(dú)的線程中運(yùn)行」,而不是在主線程中。

          ?

          因此,它不會(huì)導(dǎo)致任何阻塞用戶交互。Web Worker主要用于在Web瀏覽器中執(zhí)行耗時(shí)任務(wù),如對(duì)大量數(shù)據(jù)進(jìn)行排序、CSV導(dǎo)出、圖像處理等。

          從上圖中,如果耗時(shí)任務(wù)在主線程中執(zhí)行會(huì)阻塞UI渲染,當(dāng)用Web Worker代理耗時(shí)任務(wù)后,主線程并不會(huì)發(fā)生阻塞,也就是說「它強(qiáng)任它強(qiáng),老子Web Worker

          2. React 的并發(fā)模式

          講到這里,可能有些心細(xì)的小伙伴就會(huì)產(chǎn)生疑問。既然都是處理耗時(shí)任務(wù)。那么,React 18的并發(fā)渲染也可以達(dá)到此種目的。也就是使用React.useTransition()將耗時(shí)任務(wù)設(shè)定為過渡任務(wù),通過對(duì)某些操作標(biāo)記為「低優(yōu)先級(jí)」,在頁面渲染過程中給「高優(yōu)先級(jí)」的任務(wù)讓步。

          之前我們?cè)?/p>

          中,對(duì)React 并發(fā)有過介紹。(想了解更多可以翻閱上述文章)。這里我們就簡單闡述一下為什么React 并發(fā)只是錦上添花,缺不能藥到病除

          如果,你仔細(xì)看過上面的文章,你就會(huì)有有一個(gè)清晰的認(rèn)知:

          ?

          React并發(fā)模式并不會(huì)并行運(yùn)行任務(wù)。它會(huì)將非緊急任務(wù)移動(dòng)到過渡狀態(tài),并立即執(zhí)行緊急任務(wù)。它「使用相同的主線程」來處理它。

          ?

          下面是之前的一個(gè)示例。

          使用useTransition只是告知React,有一些操作是不緊急的,如果遇到更高級(jí)的任務(wù),不緊急的任務(wù)可以不立馬顯示,而是在處理完高優(yōu)先級(jí)任務(wù)后才進(jìn)行低優(yōu)先級(jí)任務(wù)的渲染。

          例如,如果一個(gè)表格正在渲染一個(gè)大型數(shù)據(jù)集,而用戶嘗試搜索某些內(nèi)容,React會(huì)將任務(wù)切換到用戶搜索并首先處理它。

          正如我們?cè)趫D片中看到的那樣,

          ?

          「緊急任務(wù)是通過上下文切換」來處理的

          ?

          React的并發(fā)模式,只是讓我們的項(xiàng)目「擁有了辨別優(yōu)先級(jí)的能力」,并且在「一定限制條件下」能夠快速響應(yīng)用戶操作。但是,但是,但是,如果一個(gè)「單個(gè)任務(wù)已經(jīng)超過了瀏覽器一幀的渲染時(shí)間」,那雖然設(shè)置了startTransition,但是也「無能為力」。如果存在這種情況,那就只能人為的將單個(gè)任務(wù)繼續(xù)拆分或者利用Web Worker進(jìn)行多線程處理了。

          當(dāng)使用Web Worker進(jìn)行相同任務(wù)時(shí),表格渲染會(huì)在一個(gè)獨(dú)立的線程中并行運(yùn)行。

          3. React 中使用Web Worker

          由于我們?cè)陧?xiàng)目開發(fā)時(shí),使用不同的打包工具(vite/webpack)。幸運(yùn)的是,最新版的vite/webpack都支持Web Worker了。

          我們可以通過

          • new URL()的方式 --vite/webpack都支持

            new Worker(
              new URL(
                './worker.js'
                import.meta.url
              )
            );
          • import 方式 只有vite支持

            import MyWorker from './worker?worker'

            const worker = new MyWorker()

          更詳細(xì)的處理可以參考它們的官網(wǎng)

          • vite_web_worker[1]
          • webpack_web_worker[2]

          當(dāng)然,我們?cè)陧?xiàng)目代碼中如何實(shí)例化Worker對(duì)象也有很多方式。下面就介紹兩種。

          通過引入文件路徑

          index.js

           // 創(chuàng)建一個(gè)新的Worker對(duì)象,
           // 指定要在Worker線程中執(zhí)行的腳本文件路徑
            const myWorker = new Worker(
              new URL('./worker.js'import.meta.url)
              );

            // 向Worker發(fā)送消息
            myWorker.postMessage(789789);

            // 監(jiān)聽來自Worker的消息
            myWorker.onmessage = function(event{
              console.log("來自worker的消息: ", event.data);
            };

          worker.js

          // 在Worker腳本中接收并處理消息
          self.onmessage = function(event{
              console.log("來自主線程的消息: ", event.data);
              // 執(zhí)行一些計(jì)算密集型的任務(wù)
              let result = doSomeHeavyTask(event.data);
              // 將結(jié)果發(fā)送回主線程
              self.postMessage(result);
          };

          const doSomeHeavyTask = (num) => {
            // 模擬一些計(jì)算密集型的操作
            let result = 0;
            for (let i = 0; i < num; i++) {
              result += i;
            }
            return result;
          };

          Blob 方式

          index.js

          // 定義要在Worker中執(zhí)行的腳本內(nèi)容
            const workerScript = `
              self.onmessage = function(e) {
                console.log('來自主線程的消息: ' + e.data);
                self.postMessage('向主線程發(fā)送消息: ' + 'Hello, ' + e.data);
              };
            `
          ;

            // 創(chuàng)建一個(gè)Blob對(duì)象,指定腳本內(nèi)容和類型
            const blob = new Blob(
              [workerScript], 
              { type'application/javascript' }
            );

            // 使用URL.createObjectURL()方法創(chuàng)建一個(gè)URL,用于生成Worker
            const blobURL = URL.createObjectURL(blob);

            // 生成一個(gè)新的Worker
            const worker = new Worker(blobURL);

            // 監(jiān)聽來自Worker的消息
            worker.onmessage = function(e{
              console.log('來自worker的消息: ' + e.data);
            };

            // 向Worker發(fā)送消息
            worker.postMessage('Front789');

          使用Blob構(gòu)建方式生成Web Worker有以下幾個(gè)優(yōu)勢(shì):

          優(yōu)勢(shì) 描述
          動(dòng)態(tài)生成 可以動(dòng)態(tài)地生成Worker腳本,無需保存為單獨(dú)文件,根據(jù)需要生成不同的Worker實(shí)例。
          內(nèi)聯(lián)腳本 Worker腳本嵌入到Blob對(duì)象中,直接在JavaScript代碼中定義Worker的邏輯,無需外部腳本文件。
          便捷性 更方便地創(chuàng)建和管理Worker實(shí)例,無需依賴外部文件。
          安全性 Blob對(duì)象在內(nèi)存中生成,不需要保存為實(shí)際文件,提高安全性,避免了對(duì)實(shí)際文件的依賴和管理。
          ?

          總的來說,使用Blob構(gòu)建方式生成Web Worker可以提供更靈活、便捷和安全的方式來管理和使用Worker實(shí)例。

          ?

          4. useWorker

          上面一節(jié)中,我們介紹了如何在前端項(xiàng)目中使用Web Worker。無論是使用文件導(dǎo)入的方式還是Blob的方式。都需要寫一些模板代碼。雖然能解決我們的問題,但是使用方式還是不夠優(yōu)雅。

          功能介紹

          下面,我們就介紹一種更優(yōu)雅的方式- 使用useWorker庫。

          useWorker[3]是一個(gè)庫,它使用React Hooks在簡單的配置中使用Web Worker API。它支持在不阻塞UI的情況下執(zhí)行耗時(shí)任務(wù),支持使用Promise而不是事件監(jiān)聽器。

          我們可以從官網(wǎng)看到相關(guān)的介紹信息。

          其中,WORKER_STATUS用于返回Web Worker的狀態(tài)信息。

          我們可以通過向useWorker中傳遞一個(gè)回調(diào)函數(shù),然后該函數(shù)就會(huì)在對(duì)應(yīng)的Web Worker中執(zhí)行。

          const sortNumbers = numbers => ([...numbers].sort())
          const [
            sortWorker, 
            { 
              status: sortStatus, 
              kill: killSortWorker 
            }
            ] = useWorker(sortNumbers);

          大家可以對(duì)比之前的用原生構(gòu)建Web Worker實(shí)例。我們可以拋棄冗余代碼,并且返回的函數(shù)(sortWorker)還支持Promise

          也就意味著我們使用xx.then()或者 await xx()以同步的寫法獲取異步結(jié)果。

          import React from "react";
          import { useWorker } from "@koale/useworker";

          const numbers = [...Array(5000000)].map(
            e => ~~(Math.random() * 1000000)
          );
          const sortNumbers = nums => nums.sort();

          const Example = () => {
            const [sortWorker] = useWorker(sortNumbers);

            const runSort = async () => {
              const result = await sortWorker(numbers); 
            };

            return (
              <button type="button" onClick={runSort}>
                運(yùn)行耗時(shí)任務(wù)
              </button>

            );
          };

          并且,useWorker是一個(gè)大小為3KB的庫,我們還不需要有太多的資源負(fù)擔(dān)。既然,有這么多強(qiáng)勢(shì)的功能,那我們就來看看它到底是何方神圣。

          安裝依賴

          用我們御用腳手架f_cli[4],來構(gòu)建一個(gè)前端項(xiàng)目(npx f_cli_f craete worker_demo)。

          要將useWorker()添加到React項(xiàng)目中,請(qǐng)使用以下命令:

          npm  install @koale/useworker --force

          由于useworker源碼中使用了peerDependencies指定了React版本為^16.8.0。如果大家在17/18版本的React環(huán)境下,會(huì)發(fā)生錯(cuò)誤。所以我們可以使用--force忽略版本限制。(這里大家可以放心使用,它內(nèi)部的只是用到簡單的hook)

          安裝完包后,導(dǎo)入useWorker()

          import { 
            useWorker, 
            WORKER_STATUS 
            } from "@koale/useworker";

          我們從庫中導(dǎo)入useWorkerWORKER_STATUSuseWorker()鉤子返回workerFncontroller

          • workerFn是一個(gè)允許在Web Worker中運(yùn)行函數(shù)的函數(shù)。
          • controller包含statuskill參數(shù)。
            • status參數(shù)返回Worker的狀態(tài)
            • kill函數(shù)用于終止當(dāng)前運(yùn)行的Worker

          案例展示

          讓我們通過一個(gè)示例來看看useWorker()

          使用useWorker()和主線程對(duì)大數(shù)組進(jìn)行排序

          SortingArray

          首先,創(chuàng)建一個(gè)SortingArray組件,并添加以下代碼:

          工具代碼

          // 模擬耗時(shí)任務(wù)
          const bubleSort = (arr: number[]): number[] =>{
            const len = arr.length;

            for (let i = 0; i < len; i++) {
              for (let j = 0; j < len - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                  [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
                }
              }
            }

            return arr;
          }
          const numbers = [...Array(50000)].map(() =>
            Math.floor(Math.random() * 1000000)
          );

          主要邏輯

          import React,{ useState } from "react";
          import { 
            useWorker, 
            WORKER_STATUS 
            } from "@koale/useworker";

          function SortingArray({
            const [sortStatus, setSortStatus] = useState(false);
            const [
              sortWorker, 
              { 
                status: sortWorkerStatus 
              }
              ] = useWorker(bubleSort);
              
            console.log("WebWorker status:", sortWorkerStatus);
            
            const onSortClick = () => {
              setSortStatus(true);
              const result = bubleSort(numbers);
              setSortStatus(false);
              alert('耗時(shí)任務(wù)結(jié)束!')
              console.log("處理結(jié)果", result);
            };

            const onWorkerSortClick = () => {
              sortWorker(numbers).then((result) => {
                console.log("使用WebWorker的處理結(jié)果", result);
                alert('耗時(shí)任務(wù)結(jié)束!')
              });
            };

            return (
              <div>
                <section >
                  <button
                    type="button"
                    disabled={sortStatus}
                    onClick={() =>
           onSortClick()}
                  >
                    {sortStatus ? 
                      `正在處理耗時(shí)任務(wù)...` :
                      `主線程觸發(fā)耗時(shí)任務(wù)`
                    }
                  </button>
                  <button
                    type="button"
                    disabled={sortWorkerStatus === WORKER_STATUS.RUNNING}
                    onClick={() =>
           onWorkerSortClick()}
                  >
                    {sortWorkerStatus === WORKER_STATUS.RUNNING
                      ? `正在處理耗時(shí)任務(wù)...`
                      : `使用WebWorker處理耗時(shí)任務(wù)`
                    }
                  </button>
                </section>
                <section>
                  <span style={{ color: "white" }}>
                    打開控制臺(tái)查驗(yàn)狀態(tài)信息
                  </span>
                </section>
              </div>

            );
          }

          export default SortingArray;

          我們?cè)?code style="padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">SortingArray配置了兩個(gè)操作

          1. onSortClick中按照常規(guī)處理,也就是在主線程中執(zhí)行耗時(shí)操作
          2. onWorkerSortClick 中執(zhí)行useWorker相關(guān)邏輯,并傳遞了bubleSort函數(shù)以使用Worker執(zhí)行耗時(shí)的排序操作。

          App.js

          我們App.js中引入SortingArray組件,并且為了能讓UI阻塞看的更明顯,我們用JS來操作logo文件,讓其不停的轉(zhuǎn)動(dòng),每100毫秒旋轉(zhuǎn)一次。

          • 如果是一個(gè)阻塞主線程的任務(wù),那么logo將會(huì)停止
          • 如果主線程不阻塞,那logo會(huì)一直轉(zhuǎn)動(dòng)

          import React from "react";
          import SortingArray from "./SortingArray";
          import logo from './assets/react.svg'
          import "./App.css";

          let turn = 0;

          function infiniteLoop({
            const lgoo = document.querySelector(".logo");
            turn += 8;
            lgoo.style.transform = `rotate(${turn % 360}deg)`;
          }

          export default function App({

            React.useEffect(() => {
              const loopInterval = setInterval(infiniteLoop, 100);
              return () => clearInterval(loopInterval);
            }, []);

            return (
              <>
                <div >
                  <h1 >useWorker Demo</h1>
                  <header>
                    <img src={logo} className="logo" />
                  </header>
                  <hr />
                </div>
                <div>
                  <SortingArray />
                </div>
              </>

            );
          }

          我們來看看分別點(diǎn)擊對(duì)應(yīng)按鈕會(huì)發(fā)生啥?

          上圖是耗時(shí)任務(wù)在主線程中執(zhí)行的效果。在執(zhí)行期間,動(dòng)畫效果是阻塞的,也就意味著在多個(gè)幀的時(shí)間內(nèi),瀏覽器是無法執(zhí)行額外的操作的。

          我們用Chrome-performance來探查一下性能消耗。

          我們可以看到事件:點(diǎn)擊任務(wù)花費(fèi)了7.85秒來完成整個(gè)過程,并且它阻塞了主線程7.85秒。

          而這個(gè)圖,我們使用了Web Worker,在執(zhí)行耗時(shí)任務(wù)的時(shí)候,動(dòng)畫還是執(zhí)行原來的操作。也就是操作不會(huì)阻塞。因?yàn)?code style="padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">useWorker在后臺(tái)執(zhí)行排序而不阻塞UI。這使得用戶體驗(yàn)非常流暢。

          和上面的分析方式一樣,打開Performancetab,讓我們看看這種方法的性能分析結(jié)果。

          我們截取主線程的部分?jǐn)?shù)據(jù),發(fā)現(xiàn)有任意時(shí)間段內(nèi),Scripting所占總時(shí)間的比例都很少,更大部分都是Idle也就是主線程處于空閑階段,可以隨時(shí)響應(yīng)用戶操作。

          而在對(duì)應(yīng)的worker中確是一直在執(zhí)行計(jì)算任務(wù),絲毫沒有片刻休息。

          5. Web Worker的注意點(diǎn)

          何時(shí)用Worker

          我們之前的文章講過,JS自從引入V8[5]后,在代碼執(zhí)行和內(nèi)存處理上有了更高的優(yōu)化。例如使用JIT[6],引入WebAssembly[7],熱代碼優(yōu)先編譯等。

          但是呢,針對(duì)一些特殊的場(chǎng)景,上述的方式只能提供簡單的優(yōu)化,這樣我們就需要另外的解決方案來處理這些棘手的問題。

          當(dāng)我們遇到如下情景,并有嚴(yán)重的性能問題,那就需要借助Web Worker一臂之力了

          • 圖像處理
          • 對(duì)大型數(shù)據(jù)集進(jìn)行排序或處理
          • 帶有大量數(shù)據(jù)的CSV或Excel導(dǎo)出
          • 畫布繪制
          • 任何CPU密集型任務(wù)

          Worker的限制

          這個(gè)在之前介紹Web Worker的文章就介紹過,我們就直接拿來主義了。

          • Web Worker無法訪問window對(duì)象和document
          • 當(dāng)Worker正在運(yùn)行時(shí),我們無法再次調(diào)用它,直到它完成或被終止。為了解決這個(gè)問題,我們可以創(chuàng)建兩個(gè)或更多useWorker()鉤子的實(shí)例。
          • Web Worker無法返回函數(shù),因?yàn)轫憫?yīng)是序列化的。
          • Web Worker受到終端用戶機(jī)器可用CPU核心和內(nèi)存的限制。

          后記

          「分享是一種態(tài)度」

          「全文完,既然看到這里了,如果覺得不錯(cuò),隨手點(diǎn)個(gè)贊和“在看”吧。」


          Node 社群


          •        


            我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


          • 最后不要忘了點(diǎn)個(gè)贊再走噢

          Reference

          [1]

          vite_web_worker: https://cn.vitejs.dev/guide/features.html#web-workers

          [2]

          webpack_web_worker: https://webpack.js.org/guides/web-workers/

          [3]

          useWorker: https://github.com/alewin/useWorker

          [4]

          f_cli: https://www.npmjs.com/package/f_cli_f

          [5]

          V8: https://v8.dev/

          [6]

          JIT: https://v8.dev/blog/maglev

          [7]

          WebAssembly: https://webassembly.github.io/spec/core/


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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色成人网站在线观看免费 | 玖玖在线免费观看视频 | 久久成人麻豆午夜电影 | 成人黄色三级片视频 | 日韩超碰在线 |