Back

React ์•ฑ์ด ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋Š” ์ง„์งœ ์ด์œ : Web Workers๋กœ ์„ฑ๋Šฅ ๊ฐœ์„ ํ•˜๊ธฐ

React ์•ฑ์ด ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋Š” ์ง„์งœ ์ด์œ : Web Workers๋กœ ์„ฑ๋Šฅ ๊ฐœ์„ ํ•˜๊ธฐ

์ด๋Ÿฐ ๊ฒฝํ—˜ ๋‹ค๋“ค ์žˆ์ž–์•„์š”. ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ ๋งŒ๋“œ๋Š”๋ฐ, ๋Œ€์šฉ๋Ÿ‰ ๋ฆฌ์ŠคํŠธ ํ•„ํ„ฐ๋ง์ด๋“  CSV ํŒŒ์‹ฑ์ด๋“  ๋ณต์žกํ•œ ๊ณ„์‚ฐ์ด๋“ . ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ 100๊ฑด์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์ฐฐ๋–ก์ด์—์š”. ๊ทผ๋ฐ ํ”„๋กœ๋•์…˜์—์„œ 50,000๊ฑด ๋“ค์–ด์˜ค๋‹ˆ๊นŒ UI๊ฐ€ 3์ดˆ ๋™์•ˆ ์–ผ์–ด๋ฒ„๋ฆผ. ์œ ์ €๊ฐ€ ๋ฒ„ํŠผ ํด๋ฆญํ•ด์š”. ์•„๋ฌด ๋ฐ˜์‘ ์—†์Œ. ๋˜ ํด๋ฆญํ•ด์š”. ์—ฌ์ „ํžˆ ์—†์Œ. ๊ทธ๋Ÿฌ๋‹ค ๊ฐ‘์ž๊ธฐ ํ•œ๊บผ๋ฒˆ์— ๋‹ค ํ„ฐ์ ธ์š”.

๋ฉ”์ธ ์Šค๋ ˆ๋“œ ํ„ฐ์ง€๊ธฐ ์ง์ „์ด๊ณ , ์œ ์ €๋“ค์€ ์ดํƒˆ ์ค‘์ด์—์š”.

JavaScript์˜ ๋ผˆ์•„ํ”ˆ ํ˜„์‹ค์ด์—์š”. ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋ผ์„œ์š”. ๋ชจ๋“  ์• ๋‹ˆ๋ฉ”์ด์…˜, ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ, API ์‘๋‹ต, ๋ณต์žกํ•œ ๊ณ„์‚ฐ์ด ๊ทธ ํ•˜๋‚˜๋ฟ์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ž๋ฆฌ์‹ธ์›€์„ ํ•ด์š”. ๋ฌด๊ฑฐ์šด ์ž‘์—…์ด ์Šค๋ ˆ๋“œ๋ฅผ ์ ๋ นํ•˜๋ฉด, ๊ณต๋“ค์—ฌ ๋งŒ๋“  60fps UI๊ฐ€ 0fps๋กœ ๋š ๋–จ์–ด์ ธ์š”. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ”„๋ ˆ์ž„๋„ ๋ชป ๊ทธ๋ฆฌ๊ณ  ์ž…๋ ฅ์— ๋ฐ˜์‘๋„ ๋ชป ํ•ด์š”.

ํ•ด๊ฒฐ์ฑ…? Web Workers์˜ˆ์š”. ๋น„์‹ผ ์ž‘์—…์„ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์™„์ „ํžˆ ๋นผ๋ฒ„๋ ค์š”. UI ์Šค๋ ˆ๋“œ๋Š” ๋ Œ๋”๋ง์ด๋ž‘ ์œ ์ € ๋ฐ˜์‘์— ์ง‘์ค‘ํ•˜๊ฒŒ ํ•˜๊ณ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์ˆซ์ž ๊ณ„์‚ฐํ•˜๋ฉด ๋ผ์š”.

๊ทผ๋ฐ ๋ฌธ์ œ๊ฐ€ ํ•˜๋‚˜ ์žˆ์–ด์š”. ๋Œ€๋ถ€๋ถ„์˜ Web Worker ํŠœํ† ๋ฆฌ์–ผ์ด "์ˆซ์ž ๋‘ ๊ฐœ ๋”ํ•˜๊ธฐ" ์ˆ˜์ค€ ์˜ˆ์ œ ๋ณด์—ฌ์ฃผ๊ณ  ๋์ด๊ฑฐ๋“ ์š”. ์‹ค์ œ React ์•ฑ์—์„œ Worker ์“ฐ๋ ค๊ณ  ํ•˜๋ฉด ๋ง‰ํžˆ๋Š” ๊ฒŒ ํ•œ๋‘˜์ด ์•„๋‹ˆ์—์š”:

  • ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋Š” ์–ด๋–ป๊ฒŒ ๊ณต์œ ํ•˜์ง€?
  • TypeScript ํƒ€์ž…์€?
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” ์–ด๋–ป๊ฒŒ ์ œ๋Œ€๋กœ ํ•˜์ง€?
  • Worker์—์„œ npm ํŒจํ‚ค์ง€ ์“ธ ์ˆ˜ ์žˆ์–ด?
  • ๋‚ด ์•ฑ์— ๋งž๋Š” ์•„ํ‚คํ…์ฒ˜๋Š” ๋ญ์ง€?

์ด ๊ธ€์—์„œ ๋‹ค ๋‹ค๋ค„๋ณผ๊ฒŒ์š”. ์ œ๋Œ€๋กœ ํŒŒ๋ด…์‹œ๋‹ค.

๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ฌธ์ œ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ธฐ

๋ฌธ์ œ๋ฅผ ๊ณ ์น˜๋ ค๋ฉด ๋จผ์ € ์ดํ•ดํ•ด์•ผ์ฃ . JavaScript๋Š” "๋ฉ”์ธ ์Šค๋ ˆ๋“œ"๋ผ๋Š” ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ ๋Œ์•„๊ฐ€์š”. ์ด ์Šค๋ ˆ๋“œ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ๋“ค:

  1. JavaScript ์‹คํ–‰ (์—ฌ๋Ÿฌ๋ถ„ ์ฝ”๋“œ)
  2. DOM ์—…๋ฐ์ดํŠธ (๋ Œ๋”๋ง)
  3. ์œ ์ € ์ž…๋ ฅ ์ฒ˜๋ฆฌ (ํด๋ฆญ, ํƒ€์ดํ•‘)
  4. ํƒ€์ด๋จธ๋ž‘ ์• ๋‹ˆ๋ฉ”์ด์…˜ (requestAnimationFrame, setTimeout)
  5. ๋„คํŠธ์›Œํฌ ์ฝœ๋ฐฑ (fetch ์‘๋‹ต)

500ms ๊ฑธ๋ฆฌ๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ๋‹ค๋ฅธ ๋ชจ๋“  ๊ฒŒ ๊ธฐ๋‹ค๋ ค์š”. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋‹ค์‹œ ๊ทธ๋ฆฌ์ง€๋„ ๋ชปํ•ด์š”. ํด๋ฆญ๋„ ์ฒ˜๋ฆฌ ๋ชป ํ•ด์š”. ์œ ์ € ์ž…์žฅ์—์„œ ํŽ˜์ด์ง€๊ฐ€ ์–ผ์–ด๋ฒ„๋ฆฐ ๊ฑฐ์˜ˆ์š”.

// ์ด๊ฑด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ~500ms ๋™์•ˆ ๋ธ”๋กœํ‚นํ•จ function heavyComputation(data) { // 50,000๊ฐœ ์•„์ดํ…œ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค ์น˜๋ฉด return data.map(item => { // ๋ณต์žกํ•œ ๋ณ€ํ™˜ return expensiveOperation(item); }); } // ์ด๊ฒŒ ์‹คํ–‰๋˜๋ฉด UI ์–ผ์–ด๋ฒ„๋ฆผ const result = heavyComputation(hugeDataset);

"async/await ์“ฐ๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋ƒ?"๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์—ฌ๊ธฐ์„  ์•ˆ ํ†ตํ•ด์š”:

// ์—ฌ์ „ํžˆ ๋ธ”๋กœํ‚น๋จ! async๋Š” I/O์—๋งŒ ๋„์›€๋˜๊ณ  CPU ์ž‘์—…์—” ์†Œ์šฉ์—†์Œ async function stillBlocking(data) { // ์ด ๊ณ„์‚ฐ์€ ์—ฌ์ „ํžˆ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ๋Œ์•„๊ฐ return data.map(item => expensiveOperation(item)); }

async ํ‚ค์›Œ๋“œ๋Š” ์™ธ๋ถ€ ๋ญ”๊ฐ€๋ฅผ ๊ธฐ๋‹ค๋ฆด ๋•Œ๋งŒ ๋„์›€์ด ๋ผ์š” (๋„คํŠธ์›Œํฌ, ๋””์Šคํฌ). CPU ๋จน๋Š” ์ž‘์—…์—๋Š” ์ง„์งœ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด์š”. ๊ทธ๊ฒŒ ๋ฐ”๋กœ Web Workers๊ณ ์š”.

Web Workers ๊ธฐ๋ณธ ๊ฐœ๋…

Web Worker๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ž‘ ๋ณ‘๋ ฌ๋กœ ๋Œ์•„๊ฐ€๋Š” ๋ณ„๋„์˜ JavaScript ์Šค๋ ˆ๋“œ์˜ˆ์š”. ์ž์ฒด ์ด๋ฒคํŠธ ๋ฃจํ”„, ์ž์ฒด ๊ธ€๋กœ๋ฒŒ ์Šค์ฝ”ํ”„๊ฐ€ ์žˆ๊ณ , ์ค‘์š”ํ•œ ๊ฑด DOM์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์–ด์š”.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     ๋ธŒ๋ผ์šฐ์ € ํ”„๋กœ์„ธ์Šค                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚      ๋ฉ”์ธ ์Šค๋ ˆ๋“œ          โ”‚         ์›Œ์ปค ์Šค๋ ˆ๋“œ               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  โ€ข DOM ์ ‘๊ทผ ๊ฐ€๋Šฅ         โ”‚  โ€ข DOM ์ ‘๊ทผ ๋ถˆ๊ฐ€                  โ”‚
โ”‚  โ€ข window ๊ฐ์ฒด          โ”‚  โ€ข self ๊ฐ์ฒด                      โ”‚
โ”‚  โ€ข ์œ ์ € ์ด๋ฒคํŠธ           โ”‚  โ€ข ๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ                    โ”‚
โ”‚  โ€ข ๋ Œ๋”๋ง               โ”‚  โ€ข ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ                    โ”‚
โ”‚  โ€ข React ์ƒํƒœ           โ”‚  โ€ข ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚              postMessage() / onmessage                       โ”‚
โ”‚         (structured cloning์œผ๋กœ ํ†ต์‹ )                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

์Šค๋ ˆ๋“œ ๊ฐ„ ํ†ต์‹ ์€ postMessage()๋ž‘ onmessage๋กœ ํ•ด์š”. ๋ฐ์ดํ„ฐ๋Š” "structured clone algorithm"์„ ํ†ตํ•ด ์Šค๋ ˆ๋“œ ๊ฐ„์— ๋ณต์‚ฌ๋ผ์š”. ๊ณต์œ ๊ฐ€ ์•„๋‹ˆ๋ผ ๋ณต์‚ฌ์˜ˆ์š”. ๋Œ€๋ถ€๋ถ„์˜ JavaScript ํƒ€์ž…์„ ์ง€์›ํ•˜๋Š” ๋”ฅ ์นดํ”ผ๋ผ๊ณ  ๋ณด๋ฉด ๋ผ์š”.

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ Worker ์˜ˆ์‹œ:

// worker.js self.onmessage = function(event) { const data = event.data; const result = heavyComputation(data); self.postMessage(result); }; function heavyComputation(data) { // ๋ฌด๊ฑฐ์šด ์ž‘์—… ์—ฌ๊ธฐ์„œ return data.map(item => item * 2); }
// main.js const worker = new Worker('worker.js'); worker.onmessage = function(event) { console.log('๊ฒฐ๊ณผ:', event.data); }; worker.postMessage([1, 2, 3, 4, 5]);

์ถฉ๋ถ„ํžˆ ๊ฐ„๋‹จํ•˜์ฃ ? ๊ทผ๋ฐ ์—ฌ๊ธฐ์„œ ์˜๋ฌธ์ด ์ƒ๊ฒจ์š”. React์—์„  ์–ด๋–ป๊ฒŒ ์จ? TypeScript๋Š”? ๋ณต์žกํ•œ ๋กœ์ง์€ ์–ด๋–ป๊ฒŒ ๊ณต์œ ํ•ด? ์‹ค์ „ ์†”๋ฃจ์…˜์„ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค.

๋ชจ๋˜ React์—์„œ Web Workers ์„ธํŒ…ํ•˜๊ธฐ

Vite, Next.js, Create React App ์“ฐ๊ณ  ์žˆ๋‹ค๋ฉด, ๊ฐ๊ฐ Worker ์ง€์› ๋ฐฉ์‹์ด ์ข€ ๋‹ฌ๋ผ์š”. ๋ชจ๋“  ํ™˜๊ฒฝ์—์„œ ๋จนํžˆ๋Š” ๋ชจ๋˜ํ•œ ๋ฐฉ๋ฒ•๋“ค ์•Œ๋ ค๋“œ๋ฆด๊ฒŒ์š”.

๋ฐฉ๋ฒ• 1: Blob URL๋กœ ์ธ๋ผ์ธ Worker

๊ฐ„๋‹จํ•œ ์ผ€์ด์Šค์—์„œ๋Š” ์ธ๋ผ์ธ ์ฝ”๋“œ๋กœ Worker๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”:

// utils/createWorker.ts export function createWorkerFromFunction<T, R>( fn: (data: T) => R ): (data: T) => Promise<R> { const workerCode = ` self.onmessage = function(e) { const fn = ${fn.toString()}; const result = fn(e.data); self.postMessage(result); }; `; const blob = new Blob([workerCode], { type: 'application/javascript' }); const workerUrl = URL.createObjectURL(blob); const worker = new Worker(workerUrl); return (data: T): Promise<R> => { return new Promise((resolve, reject) => { worker.onmessage = (e) => resolve(e.data); worker.onerror = (e) => reject(e); worker.postMessage(data); }); }; }

์“ฐ๋Š” ๋ฒ•:

const processInWorker = createWorkerFromFunction((numbers: number[]) => { return numbers.map(n => n * n).reduce((a, b) => a + b, 0); }); // ์ด์ œ Worker์—์„œ ๋Œ์•„๊ฐ! const sum = await processInWorker([1, 2, 3, 4, 5]);

์ œํ•œ์‚ฌํ•ญ: ํ•จ์ˆ˜๊ฐ€ ๋‹ค๋ฅธ ๋ชจ๋“ˆ importํ•˜๊ฑฐ๋‚˜ ํด๋กœ์ € ์“ธ ์ˆ˜ ์—†์–ด์š”. ์™„์ „ํžˆ ๋…๋ฆฝ์ ์ด์–ด์•ผ ํ•ด์š”.

๋ฐฉ๋ฒ• 2: Vite ๋„ค์ดํ‹ฐ๋ธŒ Worker ์ง€์›

Vite๋Š” ?worker ์ ‘๋ฏธ์‚ฌ๋กœ ๊น”๋”ํ•œ Worker ์ง€์›์„ ์ œ๊ณตํ•ด์š”:

// workers/dataProcessor.worker.ts export interface WorkerInput { data: number[]; operation: 'sum' | 'average' | 'max'; } export interface WorkerOutput { result: number; processingTime: number; } self.onmessage = (event: MessageEvent<WorkerInput>) => { const start = performance.now(); const { data, operation } = event.data; let result: number; switch (operation) { case 'sum': result = data.reduce((a, b) => a + b, 0); break; case 'average': result = data.reduce((a, b) => a + b, 0) / data.length; break; case 'max': result = Math.max(...data); break; } const output: WorkerOutput = { result, processingTime: performance.now() - start, }; self.postMessage(output); };
// hooks/useDataProcessor.ts import { useCallback, useRef, useEffect } from 'react'; import DataProcessorWorker from '../workers/dataProcessor.worker?worker'; import type { WorkerInput, WorkerOutput } from '../workers/dataProcessor.worker'; export function useDataProcessor() { const workerRef = useRef<Worker | null>(null); useEffect(() => { workerRef.current = new DataProcessorWorker(); return () => workerRef.current?.terminate(); }, []); const process = useCallback((input: WorkerInput): Promise<WorkerOutput> => { return new Promise((resolve, reject) => { if (!workerRef.current) { reject(new Error('Worker not initialized')); return; } workerRef.current.onmessage = (e) => resolve(e.data); workerRef.current.onerror = (e) => reject(e); workerRef.current.postMessage(input); }); }, []); return { process }; }

๋ฐฉ๋ฒ• 3: Comlink๋กœ ๊น”๋”ํ•œ API ๋งŒ๋“ค๊ธฐ

๊ตฌ๊ธ€ ํฌ๋กฌ ๋žฉ์Šค์—์„œ ๋งŒ๋“  Comlink๋Š” Worker๋ฅผ ์ผ๋ฐ˜ async ํ•จ์ˆ˜์ฒ˜๋Ÿผ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์š”:

// workers/imageProcessor.worker.ts import * as Comlink from 'comlink'; const api = { async processImage(imageData: ImageData): Promise<ImageData> { // ๋ฌด๊ฑฐ์šด ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ const pixels = imageData.data; for (let i = 0; i < pixels.length; i += 4) { // ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ๋ณ€ํ™˜ const avg = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3; pixels[i] = avg; // R pixels[i + 1] = avg; // G pixels[i + 2] = avg; // B } return imageData; }, async analyzeData(data: number[]): Promise<{ mean: number; stdDev: number; median: number; }> { const sorted = [...data].sort((a, b) => a - b); const mean = data.reduce((a, b) => a + b, 0) / data.length; const squaredDiffs = data.map(x => Math.pow(x - mean, 2)); const variance = squaredDiffs.reduce((a, b) => a + b, 0) / data.length; return { mean, stdDev: Math.sqrt(variance), median: sorted[Math.floor(sorted.length / 2)], }; }, }; Comlink.expose(api); export type WorkerApi = typeof api;
// hooks/useImageProcessor.ts import { useEffect, useRef } from 'react'; import * as Comlink from 'comlink'; import type { WorkerApi } from '../workers/imageProcessor.worker'; export function useImageProcessor() { const workerRef = useRef<Comlink.Remote<WorkerApi> | null>(null); useEffect(() => { const worker = new Worker( new URL('../workers/imageProcessor.worker.ts', import.meta.url), { type: 'module' } ); workerRef.current = Comlink.wrap<WorkerApi>(worker); return () => worker.terminate(); }, []); return workerRef.current; } // ์ปดํฌ๋„ŒํŠธ์—์„œ ์“ฐ๋Š” ๋ฒ• function ImageEditor() { const processor = useImageProcessor(); const handleProcess = async () => { if (!processor) return; // ๊ทธ๋ƒฅ async ํ•จ์ˆ˜ ํ˜ธ์ถœํ•˜๋“ฏ์ด! const result = await processor.processImage(imageData); setProcessedImage(result); }; }

Comlink๊ฐ€ postMessage ์ฒ˜๋ฆฌ๋ฅผ ๋‹ค ํ•ด์ฃผ๋‹ˆ๊นŒ Worker๊ฐ€ ํ›จ์”ฌ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋А๊ปด์ ธ์š”.

์‹ค์ „ ์˜ˆ์ œ: UI ์•ˆ ๋ฉˆ์ถ”๋Š” CSV ํŒŒ์„œ

๋ญ”๊ฐ€ ์‹ค์šฉ์ ์ธ ๊ฑธ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค. CSV ํŒŒ์ผ ์ž„ํฌํŠธํ•˜๋Š” ์•ฑ์„ ๋งŒ๋“ ๋‹ค๊ณ  ํ•ด๋ด์š”. ์œ ์ €๊ฐ€ 50MB์งœ๋ฆฌ, 500,000 ํ–‰ CSV๋ฅผ ์—…๋กœ๋“œํ•ด์š”. Worker ์—†์ด๋Š” ํŒŒ์‹ฑํ•˜๋Š” ๋™์•ˆ UI๊ฐ€ 5-10์ดˆ ๋™์•ˆ ๋ฉˆ์ถฐ๋ฒ„๋ ค์š”.

// workers/csvParser.worker.ts import * as Comlink from 'comlink'; interface ParseOptions { delimiter?: string; hasHeader?: boolean; } interface ParseResult { headers: string[]; rows: string[][]; rowCount: number; parseTimeMs: number; } interface ProgressCallback { (progress: number): void; } const csvParser = { async parse( csvText: string, options: ParseOptions = {}, onProgress?: ProgressCallback ): Promise<ParseResult> { const start = performance.now(); const { delimiter = ',', hasHeader = true } = options; const lines = csvText.split('\n'); const totalLines = lines.length; const headers: string[] = []; const rows: string[][] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; const values = parseCsvLine(line, delimiter); if (i === 0 && hasHeader) { headers.push(...values); } else { rows.push(values); } // 1000 ํ–‰๋งˆ๋‹ค ์ง„ํ–‰ ์ƒํ™ฉ ๋ฆฌํฌํŠธ if (onProgress && i % 1000 === 0) { onProgress(Math.round((i / totalLines) * 100)); } } return { headers, rows, rowCount: rows.length, parseTimeMs: performance.now() - start, }; }, async filter( rows: string[][], columnIndex: number, predicate: string, onProgress?: ProgressCallback ): Promise<string[][]> { const result: string[][] = []; for (let i = 0; i < rows.length; i++) { const value = rows[i][columnIndex]; if (value?.toLowerCase().includes(predicate.toLowerCase())) { result.push(rows[i]); } if (onProgress && i % 1000 === 0) { onProgress(Math.round((i / rows.length) * 100)); } } return result; }, async aggregate( rows: string[][], groupByColumn: number, aggregateColumn: number ): Promise<Map<string, number>> { const groups = new Map<string, number[]>(); for (const row of rows) { const key = row[groupByColumn]; const value = parseFloat(row[aggregateColumn]); if (!isNaN(value)) { if (!groups.has(key)) { groups.set(key, []); } groups.get(key)!.push(value); } } const result = new Map<string, number>(); for (const [key, values] of groups) { result.set(key, values.reduce((a, b) => a + b, 0)); } return result; }, }; function parseCsvLine(line: string, delimiter: string): string[] { const result: string[] = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { inQuotes = !inQuotes; } else if (char === delimiter && !inQuotes) { result.push(current.trim()); current = ''; } else { current += char; } } result.push(current.trim()); return result; } Comlink.expose(csvParser); export type CsvParser = typeof csvParser;
// hooks/useCsvParser.ts import { useEffect, useRef, useState, useCallback } from 'react'; import * as Comlink from 'comlink'; import type { CsvParser } from '../workers/csvParser.worker'; export function useCsvParser() { const workerRef = useRef<Comlink.Remote<CsvParser> | null>(null); const [progress, setProgress] = useState(0); const [isProcessing, setIsProcessing] = useState(false); useEffect(() => { const worker = new Worker( new URL('../workers/csvParser.worker.ts', import.meta.url), { type: 'module' } ); workerRef.current = Comlink.wrap<CsvParser>(worker); return () => worker.terminate(); }, []); const parseFile = useCallback(async (file: File) => { if (!workerRef.current) throw new Error('Worker not ready'); setIsProcessing(true); setProgress(0); try { const text = await file.text(); const result = await workerRef.current.parse( text, { hasHeader: true }, Comlink.proxy((p: number) => setProgress(p)) ); return result; } finally { setIsProcessing(false); setProgress(100); } }, []); return { parseFile, progress, isProcessing, parser: workerRef.current, }; }
// components/CsvImporter.tsx import { useState } from 'react'; import { useCsvParser } from '../hooks/useCsvParser'; export function CsvImporter() { const { parseFile, progress, isProcessing } = useCsvParser(); const [result, setResult] = useState<ParseResult | null>(null); const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (!file) return; const parsed = await parseFile(file); setResult(parsed); }; return ( <div> <input type="file" accept=".csv" onChange={handleFileChange} disabled={isProcessing} /> {isProcessing && ( <div className="progress-bar"> <div className="progress-fill" style={{ width: `${progress}%` }} /> <span>์ฒ˜๋ฆฌ ์ค‘... {progress}%</span> </div> )} {result && ( <div className="result"> <p>{result.rowCount.toLocaleString()} ํ–‰ ํŒŒ์‹ฑ ์™„๋ฃŒ</p> <p>์†Œ์š” ์‹œ๊ฐ„: {result.parseTimeMs.toFixed(2)}ms</p> <table> <thead> <tr> {result.headers.map((h, i) => ( <th key={i}>{h}</th> ))} </tr> </thead> <tbody> {result.rows.slice(0, 100).map((row, i) => ( <tr key={i}> {row.map((cell, j) => ( <td key={j}>{cell}</td> ))} </tr> ))} </tbody> </table> </div> )} </div> ); }

์ด์ œ CSV ํŒŒ์‹ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋Œ์•„๊ฐ€์š”. UI๋Š” ์Œฉ์Œฉํ•˜๊ฒŒ ์‚ด์•„์žˆ๊ณ , ์œ ์ €๋Š” ์ง„ํ–‰๋ฅ  ๋ณด๋ฉด์„œ ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์žˆ์–ด์š”. ๋ฒ„๋ฒ…์ž„? ์—†์–ด์š”.

๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ: Transferable Objects

์Šค๋ ˆ๋“œ ๊ฐ„์— ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•˜๋ฉด ๋А๋ ค์š”. ArrayBuffer, MessagePort, OffscreenCanvas๋Š” transferable objects๋ผ๊ณ  ํ•ด์„œ ๋ณต์‚ฌ ์—†์ด ์ด๋™์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด์š”:

// ๋ณต์‚ฌ ๋Œ€์‹  ์†Œ์œ ๊ถŒ ์ด์ „ const buffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB // ๋А๋ฆผ: ๋ฒ„ํผ ์ „์ฒด๋ฅผ ๋ณต์‚ฌ worker.postMessage({ buffer }); // ๋น ๋ฆ„: ์†Œ์œ ๊ถŒ ์ด์ „ (๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ buffer ๋ชป ์”€) worker.postMessage({ buffer }, [buffer]);

์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ด๊ฑด ์—„์ฒญ๋‚œ ์ฐจ์ด์˜ˆ์š”:

// workers/imageProcessor.worker.ts self.onmessage = async (e: MessageEvent) => { const { imageData } = e.data; // ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ const processed = applyFilter(imageData); // ๋ณต์‚ฌ ๋ง๊ณ  ์ด์ „ const buffer = processed.data.buffer; self.postMessage({ imageData: processed }, [buffer]); };
// ๋ฉ”์ธ ์Šค๋ ˆ๋“œ const offscreen = canvas.transferControlToOffscreen(); worker.postMessage({ canvas: offscreen }, [offscreen]);

๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ Worker ํ’€

Worker ํ•˜๋‚˜๋„ ์ข‹์ง€๋งŒ, ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์ผํ•˜๋ฉด ๋” ์ข‹์ฃ . ๊ฐ„๋‹จํ•œ Worker ํ’€ ์ฝ”๋“œ์˜ˆ์š”:

// utils/WorkerPool.ts export class WorkerPool<TInput, TOutput> { private workers: Worker[] = []; private queue: Array<{ data: TInput; resolve: (result: TOutput) => void; reject: (error: Error) => void; }> = []; private activeWorkers = new Set<Worker>(); constructor( private createWorker: () => Worker, private poolSize: number = navigator.hardwareConcurrency || 4 ) { for (let i = 0; i < poolSize; i++) { this.workers.push(this.createWorker()); } } async execute(data: TInput): Promise<TOutput> { return new Promise((resolve, reject) => { const availableWorker = this.workers.find( w => !this.activeWorkers.has(w) ); if (availableWorker) { this.runTask(availableWorker, data, resolve, reject); } else { this.queue.push({ data, resolve, reject }); } }); } private runTask( worker: Worker, data: TInput, resolve: (result: TOutput) => void, reject: (error: Error) => void ) { this.activeWorkers.add(worker); worker.onmessage = (e) => { resolve(e.data); this.activeWorkers.delete(worker); this.processQueue(); }; worker.onerror = (e) => { reject(new Error(e.message)); this.activeWorkers.delete(worker); this.processQueue(); }; worker.postMessage(data); } private processQueue() { if (this.queue.length === 0) return; const availableWorker = this.workers.find( w => !this.activeWorkers.has(w) ); if (availableWorker) { const { data, resolve, reject } = this.queue.shift()!; this.runTask(availableWorker, data, resolve, reject); } } async executeAll(items: TInput[]): Promise<TOutput[]> { return Promise.all(items.map(item => this.execute(item))); } terminate() { this.workers.forEach(w => w.terminate()); this.workers = []; } }

์ด๋ฏธ์ง€ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ์— ์“ฐ๋ฉด:

const pool = new WorkerPool( () => new Worker(new URL('./imageWorker.ts', import.meta.url)), 4 // 4๊ฐœ ์›Œ์ปค ๋ณ‘๋ ฌ๋กœ ); // 100๊ฐœ ์ด๋ฏธ์ง€๋ฅผ 4๊ฐœ ์›Œ์ปคํ•œํ…Œ ๋‚˜๋ˆ ์„œ ์ฒ˜๋ฆฌ const results = await pool.executeAll(images);

์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜๋“ค

์‹ค์ˆ˜ 1: ์ž‘์€ ์ž‘์—…์— Worker ๋‚จ๋ฐœ

Worker์—๋„ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์žˆ์–ด์š”. Worker ์ƒ์„ฑ, ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™”, ๊ฒฐ๊ณผ ์—ญ์ง๋ ฌํ™”์— ์‹œ๊ฐ„์ด ๋“ค์–ด์š”. ์ž‘์€ ์ž‘์—…์—์„  ์ด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๊ณ„์‚ฐ ์‹œ๊ฐ„๋ณด๋‹ค ์ปค์งˆ ์ˆ˜ ์žˆ์–ด์š”.

๊ฒฝํ—˜์น™: ์ž‘์—…์ด 16ms ์ด์ƒ (60fps์—์„œ ํ•œ ํ”„๋ ˆ์ž„) ๊ฑธ๋ฆด ๋•Œ๋งŒ Worker ์“ฐ์„ธ์š”.

// โŒ ์ด๊ฑด ์†ํ•ด - ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์ด๋“๋ณด๋‹ค ํผ worker.postMessage([1, 2, 3]); // ์ˆซ์ž 3๊ฐœ ๋”ํ•˜๊ธฐ // โœ… ์ด๊ฑด ์ด๋“ - ์ถฉ๋ถ„ํžˆ ๋ฌด๊ฑฐ์›Œ์„œ ์˜ค๋ฒ„ํ—ค๋“œ ์ƒ์‡„ worker.postMessage(bigArray); // 100,000๊ฐœ ์•„์ดํ…œ ์ฒ˜๋ฆฌ

์‹ค์ˆ˜ 2: Worker ์•ˆ ๋„๊ณ  ๋ฐฉ์น˜

Worker๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋จน์–ด์š”. ๊ธฐ์กด Worker ์•ˆ ๋„๊ณ  ์ƒˆ๋กœ ๊ณ„์† ๋งŒ๋“ค๋ฉด, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋‚˜์š”:

// โŒ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ function processData(data) { const worker = new Worker('worker.js'); worker.postMessage(data); // Worker ์•ˆ ๋”! } // โœ… ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌ function processData(data) { const worker = new Worker('worker.js'); worker.onmessage = (e) => { console.log(e.data); worker.terminate(); // ๋๋‚˜๋ฉด ์ •๋ฆฌ }; worker.postMessage(data); }

React์—์„  cleanup์—์„œ ํ•ญ์ƒ terminate:

useEffect(() => { const worker = new Worker('worker.js'); workerRef.current = worker; return () => { worker.terminate(); // โœ… ์–ธ๋งˆ์šดํŠธ๋  ๋•Œ ์ •๋ฆฌ }; }, []);

์‹ค์ˆ˜ 3: ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์•ˆ ํ•จ

Worker ์—๋Ÿฌ๋Š” ์ž๋™์œผ๋กœ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌํ•œํ…Œ ์•ˆ ๊ฐ€์š”:

// โŒ ์—๋Ÿฌ ์กฐ์šฉํžˆ ๋ฌด์‹œ๋จ worker.postMessage(data); // โœ… ์—๋Ÿฌ ์žก์•„์ฃผ๊ธฐ worker.onerror = (error) => { console.error('Worker ์—๋Ÿฌ:', error.message); // ์—๋Ÿฌ ์ฒ˜๋ฆฌ }; worker.onmessageerror = (error) => { console.error('๋ฉ”์‹œ์ง€ ์—๋Ÿฌ:', error); };

์‹ค์ˆ˜ 4: Worker ์Šค๋ ˆ๋“œ๋„ ๋ง‰์•„๋ฒ„๋ฆฌ๊ธฐ

์ž‘์—…์„ Worker๋กœ ์˜ฎ๊ฒผ๋‹ค๊ณ  ๋ธ”๋กœํ‚น์—์„œ ํ•ด๋ฐฉ๋˜๋Š” ๊ฑด ์•„๋‹ˆ์—์š”. Worker๊ฐ€ 5์ดˆ ๋™์•ˆ ๋™๊ธฐ ์ž‘์—… ๋Œ๋ฆฌ๋ฉด, ๊ทธ ์‚ฌ์ด ๋‹ค๋ฅธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ๋ชป ํ•ด์š”.

// โŒ ๊ธด ๋™๊ธฐ ์ž‘์—…์ด ๋ชจ๋“  ๋ฉ”์‹œ์ง€ ๋ง‰์Œ self.onmessage = (e) => { const result = process5MillionItems(e.data); // 5์ดˆ ๋™์•ˆ ๋ธ”๋กœํ‚น self.postMessage(result); }; // โœ… ์ฒญํฌ๋กœ ๋‚˜๋ˆ ์„œ ์ค‘๊ฐ„์ค‘๊ฐ„ ์ˆจ์‰ด ํ‹ˆ ์ฃผ๊ธฐ self.onmessage = async (e) => { const items = e.data; const results = []; const chunkSize = 10000; for (let i = 0; i < items.length; i += chunkSize) { const chunk = items.slice(i, i + chunkSize); results.push(...processChunk(chunk)); // ๋‹ค๋ฅธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ž ๊น ์–‘๋ณด await new Promise(resolve => setTimeout(resolve, 0)); // ์ง„ํ–‰๋ฅ  ๋ฆฌํฌํŠธ self.postMessage({ type: 'progress', value: i / items.length }); } self.postMessage({ type: 'complete', results }); };

Web Workers ์“ฐ๋ฉด ์•ˆ ๋˜๋Š” ๊ฒฝ์šฐ

Worker๊ฐ€ ๋งŒ๋Šฅ์€ ์•„๋‹ˆ์—์š”. ์ด๋Ÿด ๋• ํ”ผํ•˜์„ธ์š”:

  1. I/O ์ž‘์—…์ด์ง€ CPU ์ž‘์—…์ด ์•„๋‹ ๋•Œ - ๋ฐ์ดํ„ฐ fetching, DB ์ฟผ๋ฆฌ ๊ฐ™์€ ๊ฑฐ. ๊ทธ๋ƒฅ async/await ์“ฐ๋ฉด ๋ผ์š”.

  2. DOM ๊ฑด๋“œ๋ ค์•ผ ํ•  ๋•Œ - Worker๋Š” DOM ๋ชป ๋งŒ์ ธ์š”. ์ฒ˜๋ฆฌ ์ค‘๊ฐ„์— UI ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•˜๋ฉด, ๋ฉ”์ธ ์Šค๋ ˆ๋“œํ•œํ…Œ ๋ฉ”์‹œ์ง€ ๋ณด๋‚ด์•ผ ํ•ด์š”.

  3. ๋ฐ์ดํ„ฐ ์ „์†ก ๋น„์šฉ์ด ๊ณ„์‚ฐ ์ ˆ์•ฝ๋ณด๋‹ค ํด ๋•Œ - 100ms ๊ณ„์‚ฐ ์•„๋ผ๋ ค๊ณ  1GB ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™”ํ•˜๋Š” ๊ฑด ๋ณธ์ „๋„ ๋ชป ์ฐพ์•„์š”.

  4. ์ด๋ฏธ ๋น ๋ฅธ ์ž‘์—…์ผ ๋•Œ - 16ms ์•ˆ์— ๋๋‚˜๋ฉด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋” ์ปค์š”.

  5. ์˜ค๋ž˜๋œ ๋ธŒ๋ผ์šฐ์ € ์ง€์›ํ•ด์•ผ ํ•˜๋Š”๋ฐ ํด๋ฐฑ๋„ ์—†์„ ๋•Œ - ๋ธŒ๋ผ์šฐ์ € ์ง€์› ๋ฒ”์œ„ ์ฒดํฌํ•˜์„ธ์š”.

์„ฑ๋Šฅ ๋น„๊ต: Before vs After

์‹ค์ œ๋กœ 100๋งŒ ๊ฐœ ์ˆซ์ž ์ •๋ ฌํ•ด๋ณด๋ฉด:

// Worker ์—†์ด function sortData() { const start = performance.now(); const sorted = data.sort((a, b) => a - b); console.log(`๋ฉ”์ธ ์Šค๋ ˆ๋“œ: ${performance.now() - start}ms`); console.log(`์ด ์‹œ๊ฐ„ ๋™์•ˆ UI ๋ฉˆ์ถค`); } // Worker๋กœ async function sortDataWorker() { const start = performance.now(); const sorted = await workerSort(data); console.log(`Worker: ${performance.now() - start}ms`); console.log(`UI๋Š” ์Œฉ์Œฉ`); }

์ผ๋ฐ˜์ ์ธ ๋จธ์‹ ์—์„œ ๊ฒฐ๊ณผ:

  • Worker ์—†์ด: 1,200ms (UI ๋ฉˆ์ถค)
  • Worker๋กœ: 1,250ms (UI ์Œฉ์Œฉ)

์ง๋ ฌํ™” ์˜ค๋ฒ„ํ—ค๋“œ ๋•Œ๋ฌธ์— ์ด ์‹œ๊ฐ„์€ Worker๊ฐ€ ์‚ด์ง ๋” ๊ธธ์–ด์š”. ๊ทผ๋ฐ UI๊ฐ€ ์ ˆ๋Œ€ ์•ˆ ๋ฉˆ์ถ”๋‹ˆ๊นŒ ์ฒด๊ฐ ์„ฑ๋Šฅ์€ ๋น„๊ต๋„ ์•ˆ ๋ผ์š”.

์ •๋ฆฌํ•˜๋ฉด

Web Workers๋Š” ๋ธŒ๋ผ์šฐ์ € API ์ค‘์—์„œ ๊ฐ€์žฅ ์ €ํ‰๊ฐ€๋œ ๊ฒƒ ์ค‘ ํ•˜๋‚˜์˜ˆ์š”. ์ œ๋Œ€๋กœ ์“ฐ๋ฉด ๋ป‘๋ป‘ํ•œ ์•ฑ์„ ์ฐฐ๋–ก๊ฐ™์ด ๋ฐ˜์‘ํ•˜๋Š” ์•ฑ์œผ๋กœ ๋ฐ”๊ฟ”์ค˜์š”.

ํ•ต์‹ฌ ํฌ์ธํŠธ:

  1. ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ์†Œ์ค‘ํ•ด์š” - ๋ Œ๋”๋ง์ด๋ž‘ ์œ ์ € ๋ฐ˜์‘์šฉ์œผ๋กœ ์•„๊ปด๋‘์„ธ์š”.

  2. Worker๋Š” CPU ๋จน๋Š” ์ž‘์—…์šฉ - ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, ํŒŒ์‹ฑ, ๊ณ„์‚ฐ, ์ด๋ฏธ์ง€ ์กฐ์ž‘.

  3. Comlink ์“ฐ๋ฉด API๊ฐ€ ๊น”๋”ํ•ด์š” - Worker๊ฐ€ ๊ทธ๋ƒฅ async ํ•จ์ˆ˜์ฒ˜๋Ÿผ ๋А๊ปด์ ธ์š”.

  4. ๋ณต์‚ฌ ๋ง๊ณ  ์ „์†ก - ํฐ ArrayBuffer๋Š” transferable objects๋กœ.

  5. Worker ํ’€๋กœ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ - ์—ฌ๋Ÿฌ ์›Œ์ปค๊ฐ€ ๋™์‹œ์— ์ผํ•˜๋ฉด ๋” ๋น ๋ฆ„.

  6. ์ตœ์ ํ™” ์ „์— ์ธก์ •๋ถ€ํ„ฐ - Worker๋„ ์˜ค๋ฒ„ํ—ค๋“œ ์žˆ์–ด์š”. ์ด๋“์ด ์†ํ•ด๋ณด๋‹ค ํด ๋•Œ๋งŒ ์“ฐ์„ธ์š”.

  7. ์ •๋ฆฌ ์žŠ์ง€ ๋งˆ์„ธ์š” - ๋๋‚˜๋ฉด terminate ๊ผญ.

๋‹ค์Œ์— React ์•ฑ์ด ๋ฌด๊ฑฐ์šด ์ž‘์—… ๋•Œ๋ฌธ์— ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋ฉด, ๋ญ˜ ํ•ด์•ผ ํ•˜๋Š”์ง€ ์ด์ œ ์•Œ๊ฒ ์ฃ ? ๊ทธ ์ž‘์—… Worker๋กœ ๋นผ๊ณ , ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋น„์›Œ๋‘๊ณ , ์œ ์ €ํ•œํ…Œ ๋ถ€๋“œ๋Ÿฌ์šด ๊ฒฝํ—˜ ์„ ๋ฌผํ•˜์„ธ์š”.

web-workersreactjavascriptperformancemultithreadingoptimization