웹 작업자가 있는 React 앱에서 장기 실행 작업 관리하기

게시 됨: 2022-03-10
빠른 요약 ↬ 이 튜토리얼에서는 Web Worker API를 사용하여 Web Worker를 활용하는 샘플 웹 앱을 빌드하여 JavaScript 앱에서 시간 소모적이고 UI 차단 작업을 관리하는 방법을 배울 것입니다. 마지막으로 모든 것을 React 애플리케이션으로 전송하는 것으로 기사를 마칠 것입니다.

웹 애플리케이션의 경우 응답 시간이 중요합니다. 사용자는 앱이 무엇을 하든 즉각적인 응답을 요구합니다. 사람의 이름만 표시하든 숫자를 계산하든 웹 앱 사용자는 앱이 매번 명령에 응답할 것을 요구합니다. JavaScript의 단일 스레드 특성을 감안할 때 때로는 달성하기 어려울 수 있습니다. 그러나 이 기사에서는 Web Worker API를 활용하여 더 나은 경험을 제공하는 방법을 배웁니다.

이 글을 쓰면서 저는 다음과 같은 가정을 했습니다.

  1. 따라갈 수 있으려면 JavaScript와 문서 API에 대해 어느 정도 익숙해야 합니다.
  2. 또한 Create React App을 사용하여 새로운 React 프로젝트를 성공적으로 시작할 수 있도록 React에 대한 실무 지식이 있어야 합니다.

이 주제에 대해 더 많은 통찰력이 필요하면 "추가 리소스" 섹션에 여러 링크를 포함하여 빠르게 이해할 수 있도록 돕습니다.

먼저 웹 작업자를 시작하겠습니다.

웹 워커란?

웹 작업자와 해결해야 하는 문제를 이해하려면 런타임에 JavaScript 코드가 실행되는 방식을 이해해야 합니다. 런타임 동안 JavaScript 코드는 차례대로 순차적으로 실행됩니다. 코드 조각이 끝나면 줄의 다음 코드가 실행되기 시작하는 식입니다. 기술적인 측면에서 JavaScript는 단일 스레드라고 합니다. 이 동작은 코드의 일부가 실행을 시작하면 그 뒤에 오는 모든 코드가 해당 코드가 실행을 완료할 때까지 기다려야 함을 의미합니다. 따라서 모든 코드 라인은 그 뒤에 오는 다른 모든 것의 실행을 "차단"합니다. 따라서 모든 코드 조각이 가능한 한 빨리 완료되는 것이 바람직합니다. 일부 코드가 완료하는 데 너무 오래 걸리면 프로그램이 작동을 멈춘 것처럼 보일 것입니다. 브라우저에서 이것은 정지되고 응답하지 않는 페이지로 나타납니다. 일부 극단적인 경우에는 탭이 완전히 정지됩니다.

1차선에서 운전한다고 상상해보십시오. 앞의 운전자 중 누군가가 어떤 이유로 움직이지 않으면 교통 체증이 발생합니다. Java와 같은 프로그램을 사용하면 트래픽이 다른 차선에서 계속될 수 있습니다. 따라서 Java는 다중 스레드라고 합니다. 웹 작업자는 JavaScript에 다중 스레드 동작 을 가져오려는 시도입니다.

아래 스크린샷은 Web Worker API가 많은 브라우저에서 지원됨을 보여주므로 안심하고 사용할 수 있습니다.

웹 작업자를 위한 브라우저 지원 차트 표시
웹 작업자 브라우저 지원. (큰 미리보기)

웹 워커는 UI를 방해하지 않고 백그라운드 스레드에서 실행되며 이벤트 핸들러를 통해 웹 워커를 생성한 코드와 통신합니다.

웹 워커에 대한 훌륭한 정의는 MDN에서 가져왔습니다.

"워커는 이름이 지정된 JavaScript 파일을 실행하는 생성자(예: Worker() 를 사용하여 생성된 객체입니다. 이 파일에는 작업자 스레드에서 실행될 코드가 포함되어 있습니다. 작업자는 현재 window 다른 전역 컨텍스트에서 실행됩니다. 따라서 , window 바로 가기를 사용하여 현재 전역 범위를 가져옵니다( Worker 내의 self 대신 오류를 반환합니다."

작업자는 Worker 생성자를 사용하여 생성됩니다.

 const worker = new Worker('worker-file.js')

일부 예외를 제외하고는 대부분의 코드를 웹 작업자 내에서 실행할 수 있습니다. 예를 들어 작업자 내부에서 DOM을 조작할 수 없습니다. document API에 대한 액세스 권한이 없습니다.

작업자와 작업자를 생성하는 스레드는 postMessage() 메서드를 사용하여 서로에게 메시지를 보냅니다. 마찬가지로 onmessage 이벤트 핸들러를 사용하여 메시지에 응답합니다. 이 차이를 파악하는 것이 중요합니다. 메시지 전송은 방법을 사용하여 수행됩니다. 메시지를 수신하려면 이벤트 핸들러가 필요합니다. 수신 중인 메시지는 이벤트의 data 속성에 포함됩니다. 다음 섹션에서 이에 대한 예를 살펴보겠습니다. 그러나 우리가 논의한 종류의 작업자를 "전담 작업자"라고 빠르게 언급하겠습니다. 이것은 작업자가 호출한 스크립트에만 액세스할 수 있음을 의미합니다. 여러 스크립트에서 액세스할 수 있는 작업자를 가질 수도 있습니다. 이를 공유 작업자라고 하며 아래와 같이 SharedWorker 생성자를 사용하여 생성됩니다.

 const sWorker = new SharedWorker('shared-worker-file.js')

작업자에 대해 자세히 알아보려면 이 MDN 문서를 참조하세요. 이 기사의 목적은 웹 작업자 사용을 시작하는 것입니다. n번째 피보나치 수를 계산하여 알아보겠습니다.

점프 후 더! 아래에서 계속 읽기 ↓

N번째 피보나치 수 계산하기

참고: 이 섹션과 다음 두 섹션에서는 VSCode의 Live Server를 사용하여 앱을 실행합니다. 당신은 확실히 다른 것을 사용할 수 있습니다.

기다리시던 섹션입니다. 우리는 마침내 웹 작업자가 작동하는 것을 보기 위해 몇 가지 코드를 작성할 것입니다. 글쎄, 그렇게 빠르지 않습니다. 우리는 웹 워커가 해결하는 종류의 문제에 부딪히지 않는 한 웹 워커가 하는 일에 감사하지 않을 것입니다. 이 섹션에서는 예제 문제를 보고 다음 섹션에서는 웹 작업자가 더 나은 작업을 수행하는 데 어떻게 도움이 되는지 알아보겠습니다.

사용자가 n번째 피보나치 수를 계산할 수 있는 웹 앱을 구축하고 있다고 상상해 보십시오. '피보나치 수'라는 용어를 처음 접하는 경우 여기에서 자세한 내용을 읽을 수 있지만 요약하면 피보나치 수는 각 숫자가 앞의 두 숫자의 합이 되는 일련의 숫자입니다.

수학적으로는 다음과 같이 표현됩니다.

따라서 시퀀스의 처음 몇 개 숫자는 다음과 같습니다.

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ...

일부 소스에서 시퀀스는 F 0 = 0 에서 시작하며 이 경우 아래 공식은 n > 1 에 대해 유지됩니다.

이 기사에서는 F 1 = 1에서 시작할 것입니다. 공식에서 바로 볼 수 있는 한 가지는 숫자가 재귀 패턴을 따른다는 것입니다. 이제 당면한 작업은 n번째 피보나치 수(FN)를 계산하는 재귀 함수를 작성하는 것입니다.

몇 번 시도하면 아래 기능을 쉽게 생각해낼 수 있을 거라 생각합니다.

 const fib = n => { if (n < 2) { return n // or 1 } else { return fib(n - 1) + fib(n - 2) } }

기능은 간단합니다. n이 2보다 작으면 n(또는 1)을 반환하고, 그렇지 않으면 n-1n-2 FN의 합계를 반환합니다. 화살표 함수와 삼항 연산자를 사용하여 한 줄짜리를 만들 수 있습니다.

 const fib = n => (n < 2 ? n : fib(n-1) + fib(n-2))

이 함수의 시간 복잡도는 0(2 n ) 입니다. 이것은 단순히 n의 값이 증가함에 따라 합계를 계산하는 데 필요한 시간이 기하급수적으로 증가한다는 것을 의미합니다. 이것은 n의 큰 값에 대해 잠재적으로 UI를 방해할 수 있는 정말 오래 실행되는 작업을 만듭니다. 실제로 이것을 봅시다.

참고 : 이것이 이 특정 문제를 해결하는 가장 좋은 방법은 아닙니다. 이 방법을 사용하기로 선택한 것은 이 기사의 목적을 위한 것입니다.

시작하려면 새 폴더를 만들고 원하는 이름을 지정합니다. 이제 해당 폴더 안에 src/ 폴더를 만듭니다. 또한 루트 폴더에 index.html 파일을 만듭니다. src/ 폴더 안에 index.js 파일을 생성합니다.

index.html 을 열고 다음 HTML 코드를 추가합니다.

 <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="heading-container"> <h1>Computing the nth Fibonnaci number</h1> </div> <div class="body-container"> <p id='error' class="error"></p> <div class="input-div"> <input id='number-input' class="number-input" type='number' placeholder="Enter a number" /> <button id='submit-btn' class="btn-submit">Calculate</button> </div> <div id='results-container' class="results"></div> </div> <script src="/src/index.js"></script> </body> </html>

이 부분은 매우 간단합니다. 먼저 제목이 있습니다. 그런 다음 입력과 버튼이 있는 컨테이너가 있습니다. 사용자는 숫자를 입력한 다음 "계산"을 클릭합니다. 또한 계산 결과를 저장할 컨테이너가 있습니다. 마지막으로 script 태그에 src/index.js 파일을 포함합니다.

스타일시트 링크를 삭제할 수 있습니다. 하지만 시간이 없다면 사용할 수 있는 CSS를 정의했습니다. 루트 폴더에 styles.css 파일을 만들고 아래 스타일을 추가하기만 하면 됩니다.

 body { margin: 0; padding: 0; box-sizing: border-box; } .body-container, .heading-container { padding: 0 20px; } .heading-container { padding: 20px; color: white; background: #7a84dd; } .heading-container > h1 { margin: 0; } .body-container { width: 50% } .input-div { margin-top: 15px; margin-bottom: 15px; display: flex; align-items: center; } .results { width: 50vw; } .results>p { font-size: 24px; } .result-div { padding: 5px 10px; border-radius: 5px; margin: 10px 0; background-color: #e09bb7; } .result-div p { margin: 5px; } span.bold { font-weight: bold; } input { font-size: 25px; } p.error { color: red; } .number-input { padding: 7.5px 10px; } .btn-submit { padding: 10px; border-radius: 5px; border: none; background: #07f; font-size: 24px; color: white; cursor: pointer; margin: 0 10px; }

이제 src/index.js 를 열어 천천히 개발해 봅시다. 아래 코드를 추가합니다.

 const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)); const ordinal_suffix = (num) => { // 1st, 2nd, 3rd, 4th, etc. const j = num % 10; const k = num % 100; switch (true) { case j === 1 && k !== 11: return num + "st"; case j === 2 && k !== 12: return num + "nd"; case j === 3 && k !== 13: return num + "rd"; default: return num + "th"; } }; const textCont = (n, fibNum, time) => { const nth = ordinal_suffix(n); return ` <p id='timer'>Time: <span class='bold'>${time} ms</span></p> <p><span class="bold" id='nth'>${nth}</span> fibonnaci number: <span class="bold" id='sum'>${fibNum}</span></p> `; };

여기에 세 가지 기능이 있습니다. 첫 번째는 n번째 FN을 계산하기 위해 앞에서 본 함수입니다. 두 번째 기능은 정수에 적절한 접미사를 첨부하는 유틸리티 기능입니다. 세 번째 함수는 몇 가지 인수를 취하고 나중에 DOM에 삽입할 마크업을 출력합니다. 첫 번째 인수는 FN이 계산되는 숫자입니다. 두 번째 인수는 계산된 FN입니다. 마지막 인수는 계산을 수행하는 데 걸리는 시간입니다.

여전히 src/index.js 에서 이전 코드 바로 아래에 아래 코드를 추가합니다.

 const errPar = document.getElementById("error"); const btn = document.getElementById("submit-btn"); const input = document.getElementById("number-input"); const resultsContainer = document.getElementById("results-container"); btn.addEventListener("click", (e) => { errPar.textContent = ''; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } const startTime = new Date().getTime(); const sum = fib(num); const time = new Date().getTime() - startTime; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, sum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); });

먼저 document API를 사용하여 HTML 파일에서 DOM 노드를 확보합니다. 오류 메시지를 표시할 단락에 대한 참조를 얻습니다. 입력; 계산 버튼과 결과를 표시할 컨테이너입니다.

다음으로 "클릭" 이벤트 핸들러를 버튼에 연결합니다. 버튼을 클릭하면 입력 요소 내부에 있는 것을 가져와서 숫자로 변환하고, 2보다 작은 값을 얻으면 오류 메시지를 표시하고 반환합니다. 2보다 큰 숫자를 얻으면 계속 진행합니다. 먼저 현재 시간을 기록합니다. 그런 다음 FN을 계산합니다. 완료되면 계산에 소요된 시간을 나타내는 시간 차이를 얻습니다. 코드의 나머지 부분에서 새 div 를 만듭니다. 그런 다음 내부 HTML을 앞에서 정의한 textCont() 함수의 출력으로 설정합니다. 마지막으로 스타일 지정을 위해 클래스를 추가하고 결과 컨테이너에 추가합니다. 이것의 효과는 각 계산이 이전 계산 아래의 별도 div 에 표시된다는 것입니다.

최대 43까지 계산된 피보나치 수 표시
일부 피보나치 수. (큰 미리보기)

숫자가 증가함에 따라 계산 시간도 (기하급수적으로) 증가함을 알 수 있습니다. 예를 들어, 30에서 35로 계산 시간이 13ms에서 130ms로 증가했습니다. 우리는 여전히 이러한 작업을 "빠른" 것으로 간주할 수 있습니다. 40에서 우리는 1초 이상의 계산 시간을 봅니다. 내 컴퓨터에서 여기에서 페이지가 응답하지 않는 것을 알아차리기 시작합니다. 이 시점에서 계산이 진행되는 동안 더 이상 페이지와 상호 작용할 수 없습니다. 입력에 집중하거나 다른 작업을 수행할 수 없습니다.

JavaScript가 단일 스레드라고 이야기했을 때를 기억하십니까? 글쎄, 그 쓰레드는 이 장기 실행 계산에 의해 "차단"되었으므로 다른 모든 것은 완료될 때까지 "기다려야" 한다. 그것은 당신의 컴퓨터에서 더 낮거나 더 높은 값에서 시작할 수 있지만, 당신은 그 지점에 도달할 수밖에 없습니다. 44를 계산하는 데 거의 10초가 걸렸습니다. 웹 앱에서 수행할 다른 작업이 있는 경우 사용자는 계속하기 전에 Fib(44)가 완료될 때까지 기다려야 합니다. 그러나 해당 계산을 처리하기 위해 웹 작업자를 배포한 경우 사용자는 실행되는 동안 다른 작업을 계속할 수 있습니다.

이제 웹 작업자가 이 문제를 극복하는 데 어떻게 도움이 되는지 봅시다.

실행 중인 웹 작업자의 예

이 섹션에서는 n번째 FN을 계산하는 작업을 웹 작업자에게 위임합니다. 이것은 메인 스레드를 비우고 계산이 진행되는 동안 UI 응답성을 유지하는 데 도움이 됩니다.

웹 작업자를 시작하는 것은 놀라울 정도로 간단합니다. 방법을 봅시다. 새 파일 src/fib-worker.js 를 만듭니다. 그리고 다음 코드를 입력합니다.

 const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)); onmessage = (e) => { const { num } = e.data; const startTime = new Date().getTime(); const fibNum = fib(num); postMessage({ fibNum, time: new Date().getTime() - startTime, }); };

n번째 피보나치 수인 fib 를 계산하는 함수를 이 파일 내부로 옮겼습니다. 이 파일은 웹 작업자가 실행합니다.

웹 작업자란 무엇인가 섹션에서 웹 작업자 와 그 부모가 onmessage 이벤트 핸들러와 postMessage() 메서드를 사용하여 통신한다고 언급했습니다. 여기서는 onmessage 이벤트 핸들러를 사용하여 상위 스크립트의 메시지를 수신합니다. 메시지를 받으면 이벤트의 데이터 속성에서 숫자를 구조화합니다. 다음으로 현재 시간을 얻고 계산을 시작합니다. 결과가 준비되면 postMessage() 메서드를 사용하여 결과를 상위 스크립트에 다시 게시합니다.

src/index.js 를 엽니다. 몇 가지를 변경해 보겠습니다.

 ... const worker = new window.Worker("src/fib-worker.js"); btn.addEventListener("click", (e) => { errPar.textContent = ""; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, fibNum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); }; });

가장 먼저 할 일은 Worker 생성자를 사용하여 웹 작업자를 만드는 것입니다. 그런 다음 버튼의 이벤트 리스너 내에서 worker.postMessage({ num }) 를 사용하여 작업자에게 번호를 보냅니다. 그런 다음 작업자의 오류를 수신 대기하는 함수를 설정합니다. 여기서는 단순히 오류를 반환합니다. DOM에 표시하는 것과 같이 원하는 경우 더 많은 작업을 수행할 수 있습니다. 다음으로 작업자의 메시지를 수신합니다. 메시지를 받으면 timefibNum 을 구조화하고 DOM에 표시하는 프로세스를 계속합니다.

웹 작업자 내부에서 onmessage 이벤트는 작업자 범위에서 사용할 수 있으므로 self.onmessageself.postMessage() 로 작성할 수 있습니다. 그러나 부모 스크립트에서는 이것을 작업자 자체에 첨부해야 합니다.

아래 스크린샷에서 Chrome 개발자 도구의 소스 탭에 웹 작업자 파일이 표시됩니다. 주의해야 할 점은 어떤 숫자를 입력하든 UI가 반응을 유지한다는 것입니다. 이 동작은 웹 작업자의 마법입니다.

활성 웹 작업자 파일 보기
실행 중인 웹 작업자 파일입니다. (큰 미리보기)

우리는 웹 앱으로 많은 발전을 이루었습니다. 하지만 우리가 그것을 더 좋게 만들기 위해 할 수 있는 일이 또 있습니다. 현재 구현에서는 단일 작업자를 사용하여 모든 계산을 처리합니다. 실행 중에 새 메시지가 오면 이전 메시지가 대체됩니다. 이 문제를 해결하기 위해 각 호출에 대해 새 작업자를 만들어 FN을 계산할 수 있습니다. 다음 섹션에서 이를 수행하는 방법을 살펴보겠습니다.

여러 웹 작업자와 작업

현재 단일 작업자로 모든 요청을 처리하고 있습니다. 따라서 들어오는 요청은 아직 완료되지 않은 이전 요청을 대체합니다. 이제 우리가 원하는 것은 모든 요청에 ​​대해 새로운 웹 작업자를 생성하도록 약간의 변경을 하는 것입니다. 작업이 완료되면 이 작업자를 죽일 것입니다.

src/index.js 를 열고 웹 작업자를 생성하는 라인을 버튼의 클릭 이벤트 핸들러 내부로 이동합니다. 이제 이벤트 핸들러는 아래와 같아야 합니다.

 btn.addEventListener("click", (e) => { errPar.textContent = ""; const num = window.Number(input.value); if (num < 2) { errPar.textContent = "Please enter a number greater than 2"; return; } const worker = new window.Worker("src/fib-worker.js"); // this line has moved inside the event handler worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; const resultDiv = document.createElement("div"); resultDiv.innerHTML = textCont(num, fibNum, time); resultDiv.className = "result-div"; resultsContainer.appendChild(resultDiv); worker.terminate() // this line terminates the worker }; });

우리는 두 가지를 변경했습니다.

  1. 우리는 이 줄을 const worker = new window.Worker("src/fib-worker.js") 버튼의 클릭 이벤트 핸들러 내부로 옮겼습니다.
  2. 작업이 끝나면 작업자를 버리기 위해 이 line worker.terminate() 를 추가했습니다.

따라서 버튼을 클릭할 때마다 계산을 처리할 새 작업자를 만듭니다. 따라서 입력을 계속 변경할 수 있으며 계산이 완료되면 각 결과가 화면에 표시됩니다. 아래 스크린샷에서 20과 30에 대한 값이 45보다 먼저 나타나는 것을 볼 수 있습니다. 하지만 저는 45를 먼저 시작했습니다. 함수가 20과 30에 대해 반환되면 결과가 게시되고 작업자가 종료됩니다. 모든 것이 완료되면 소스 탭에 작업자가 없어야 합니다.

해고된 작업자의 피보나치 수 표시
여러 독립 노동자의 그림입니다. (큰 미리보기)

여기에서 이 기사를 끝낼 수 있지만 이것이 반응 앱이라면 어떻게 웹 작업자를 앱으로 가져올 수 있을까요? 그것이 다음 섹션의 초점입니다.

React의 웹 작업자

시작하려면 CRA를 사용하여 새로운 반응 앱을 만드세요. fib-worker.js 파일을 반응 앱의 public/ 폴더에 복사합니다. 여기에 파일을 넣는 것은 React 앱이 단일 페이지 앱이라는 사실에서 비롯됩니다. 반응 응용 프로그램에서 작업자를 사용하는 것과 관련된 유일한 것입니다. 여기에서 뒤따르는 모든 것은 순수한 React입니다.

src/ 폴더에서 helpers.js 파일을 만들고 여기에서 ordinal_suffix() 함수를 내보냅니다.

 // src/helpers.js export const ordinal_suffix = (num) => { // 1st, 2nd, 3rd, 4th, etc. const j = num % 10; const k = num % 100; switch (true) { case j === 1 && k !== 11: return num + "st"; case j === 2 && k !== 12: return num + "nd"; case j === 3 && k !== 13: return num + "rd"; default: return num + "th"; } };

우리 앱은 어떤 상태를 유지해야 하므로 src/reducer.js 다른 파일을 만들고 상태 리듀서에 붙여넣습니다.

 // src/reducers.js export const reducer = (state = {}, action) => { switch (action.type) { case "SET_ERROR": return { ...state, err: action.err }; case "SET_NUMBER": return { ...state, num: action.num }; case "SET_FIBO": return { ...state, computedFibs: [ ...state.computedFibs, { id: action.id, nth: action.nth, loading: action.loading }, ], }; case "UPDATE_FIBO": { const curr = state.computedFibs.filter((c) => c.id === action.id)[0]; const idx = state.computedFibs.indexOf(curr); curr.loading = false; curr.time = action.time; curr.fibNum = action.fibNum; state.computedFibs[idx] = curr; return { ...state }; } default: return state; } };

각 작업 유형을 차례로 살펴보겠습니다.

  1. SET_ERROR : 트리거될 때 오류 상태를 설정합니다.
  2. SET_NUMBER : 입력 상자의 값을 state로 설정합니다.
  3. SET_FIBO : 계산된 FN 배열에 새 항목을 추가합니다.
  4. UPDATE_FIBO : 여기에서 특정 항목을 찾아 계산된 FN과 계산하는 데 걸린 시간이 있는 새 개체로 대체합니다.

우리는 곧 이 감속기를 사용할 것입니다. 그 전에 계산된 FN을 표시할 구성 요소를 만들어 보겠습니다. 새 파일 src/Results.js 를 만들고 아래 코드를 붙여넣습니다.

 // src/Results.js import React from "react"; export const Results = (props) => { const { results } = props; return ( <div className="results-container"> {results.map((fb) => { const { id, nth, time, fibNum, loading } = fb; return ( <div key={id} className="result-div"> {loading ? ( <p> Calculating the{" "} <span className="bold"> {nth} </span>{" "} Fibonacci number... </p> ) : ( <> <p> Time: <span className="bold">{time} ms</span> </p> <p> <span className="bold"> {nth} </span>{" "} fibonnaci number:{" "} <span className="bold"> {fibNum} </span> </p> </> )} </div> ); })} </div> ); };

이 변경으로 이전 index.html 파일을 jsx로 변환하는 프로세스를 시작합니다. 이 파일에는 하나의 책임이 있습니다. 계산된 FN을 나타내는 객체 배열을 가져와서 표시합니다. 우리가 이전에 가지고 있던 것과 유일한 차이점은 loading state 의 도입입니다. 이제 계산이 실행 중일 때 로딩 상태를 표시하여 사용자에게 무언가가 일어나고 있음을 알립니다.

src/App.js 내부의 코드를 업데이트하여 최종 조각을 넣어봅시다. 코드가 다소 길기 때문에 두 단계로 수행하겠습니다. 첫 번째 코드 블록을 추가해 보겠습니다.

 import React from "react"; import "./App.css"; import { ordinal_suffix } from "./helpers"; import { reducer } from './reducer' import { Results } from "./Results"; function App() { const [info, dispatch] = React.useReducer(reducer, { err: "", num: "", computedFibs: [], }); const runWorker = (num, id) => { dispatch({ type: "SET_ERROR", err: "" }); const worker = new window.Worker('./fib-worker.js') worker.postMessage({ num }); worker.onerror = (err) => err; worker.onmessage = (e) => { const { time, fibNum } = e.data; dispatch({ type: "UPDATE_FIBO", id, time, fibNum, }); worker.terminate(); }; }; return ( <div> <div className="heading-container"> <h1>Computing the nth Fibonnaci number</h1> </div> <div className="body-container"> <p className="error"> {info.err} </p> // ... next block of code goes here ... // <Results results={info.computedFibs} /> </div> </div> ); } export default App;

평소와 같이 수입품을 수입합니다. 그런 다음 useReducer 후크를 사용하여 상태 및 업데이터 기능을 인스턴스화합니다. 그런 다음 숫자와 ID를 사용하고 해당 숫자에 대한 FN을 계산하기 위해 웹 작업자를 호출하도록 설정하는 함수 runWorker() 를 정의합니다.

작업자를 생성하기 위해 작업자 생성자에 상대 경로를 전달합니다. 런타임에 React 코드는 public/index.html 파일에 첨부되므로 동일한 디렉토리에서 fib-worker.js 파일을 찾을 수 있습니다. 계산이 완료되면( worker.onmessage 에 의해 트리거됨) UPDATE_FIBO 작업이 전달되고 작업자는 나중에 종료됩니다. 지금 우리가 가지고 있는 것은 이전에 가졌던 것과 크게 다르지 않습니다.

이 구성 요소의 반환 블록에서 이전과 동일한 HTML을 렌더링합니다. 또한 렌더링을 위해 계산된 숫자 배열을 <Results /> 구성 요소에 전달합니다.

return 문 안에 마지막 코드 블록을 추가해 보겠습니다.

 <div className="input-div"> <input type="number" value={info.num} className="number-input" placeholder="Enter a number" onChange={(e) => dispatch({ type: "SET_NUMBER", num: window.Number(e.target.value), }) } /> <button className="btn-submit" onClick={() => { if (info.num < 2) { dispatch({ type: "SET_ERROR", err: "Please enter a number greater than 2", }); return; } const id = info.computedFibs.length; dispatch({ type: "SET_FIBO", id, loading: true, nth: ordinal_suffix(info.num), }); runWorker(info.num, id); }} > Calculate </button> </div>

info.num 상태 변수를 업데이트하기 위해 입력에 onChange 핸들러를 설정했습니다. 버튼에서 onClick 이벤트 핸들러를 정의합니다. 버튼을 클릭하면 숫자가 2보다 큰지 확인합니다. runWorker() 를 호출하기 전에 먼저 계산된 FN 배열에 항목을 추가하는 작업을 전달합니다. 작업자가 작업을 완료하면 이 항목이 업데이트됩니다. 이런 식으로 모든 항목은 이전과 달리 목록에서 위치를 유지합니다.

마지막으로 이전의 styles.css 내용을 복사하고 App.css 의 내용을 교체합니다.

이제 모든 것이 준비되었습니다. 이제 반응 서버를 시작하고 몇 가지 숫자를 가지고 놀아보십시오. UX 개선 사항인 로딩 상태에 주목하세요. 또한 최대 1000까지 숫자를 입력하고 "계산"을 클릭해도 UI가 응답하는 상태를 유지합니다.

작업자가 활성 상태인 동안 로드 상태를 표시합니다.
로드 상태 및 활성 웹 작업자를 표시합니다. (큰 미리보기)

로드 상태와 활성 작업자를 확인합니다. 46번째 값이 계산되면 작업자가 종료되고 로드 상태가 최종 결과로 바뀝니다.

  • 이 React 앱의 소스 코드는 Github에서 사용할 수 있으며 vercel에 호스팅된 앱이 있습니다.

결론

휴! 긴 여정이었으므로 마무리하도록 하겠습니다. 웹 작업자를 사용하는 다른 방법을 배우려면 웹 작업자를 위한 MDN 항목(아래 리소스 목록 참조)을 살펴보시기 바랍니다.

이 기사에서 우리는 웹 워커가 무엇이고 그들이 해결해야 하는 문제의 종류에 대해 배웠습니다. 또한 일반 JavaScript를 사용하여 구현하는 방법도 보았습니다. 마지막으로 React 애플리케이션에서 웹 작업자를 구현하는 방법을 보았습니다.

이 훌륭한 API를 활용하여 사용자에게 더 나은 경험을 제공하는 것이 좋습니다.

추가 리소스

  • Console.time() , MDN 웹 문서
  • {JSON} 자리 표시자, 공식 웹사이트
  • 웹 작업자 사용, MDN 웹 문서
  • 피보나치 수, Wikipedia
  • 조건부(삼항) 연산자, MDN 웹 문서
  • Document , 웹 API, MDN 웹 문서
  • 시작하기, React 앱 만들기(문서)
  • Function.prototype.toString() , MDN 웹 문서
  • IIFE, MDN 웹 문서
  • workerSetup.js , 굉장한 Fullstack 튜토리얼, GitHub
  • "웹 작업자를 사용한 JavaScript의 병렬 프로그래밍", Uday Hiwarale, Medium