WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

Web Workersでタスクオフロード完全ガイド|postMessage・Comlink・INP改善を解説

この記事でわかること

  • メインスレッドがブロックされる原因と Core Web Vitals(INP・LCP・TBT)への影響
  • postMessage による Worker 基本通信と CSV パースのオフロード実装
  • TransferableObjects(ゼロコピー転送)で大きなデータを高速に渡す方法
  • Comlink ライブラリで Worker を通常の async 関数のように呼び出す方法
  • Worker Pool・React カスタム Hook・OMT アーキテクチャの実践パターン
生徒

重い計算処理をするとUIがフリーズしてしまいます。スクロールも止まって最悪なんですが……

ペン博士

それはメインスレッドがブロックされているんじゃ!Web Workersを使えば重い処理を別スレッドに移してUIをフリーズさせなくできるぞ。Core Web VitalsのINPやLCPの改善にも直結するから、パフォーマンスを本気で上げたいなら必須の技術じゃ!

結論から言うと、Web Workers はメインスレッドをブロックする重い処理を別スレッドに移すことで UI のフリーズを防ぎ、Core Web Vitals(INP・LCP・TBT)を直接改善します。postMessage → TransferableObjects → Comlink の順に導入難易度が上がりますが、どれも段階的に導入できます。

本記事では、Web Workers の仕組み・postMessage による通信・TransferableObjects によるゼロコピー転送・Comlink ライブラリの活用・Worker Pool・INP/LCP への影響・OMT アーキテクチャ・React/TypeScript での実践パターンを完全解説します。

あわせて読みたい:
WebAssembly(Wasm)フロントエンド高速化の実践ガイド(パフォーマンス最適化)


目次

メインスレッドの問題と Web Workers の役割

ブラウザは1つのタブに対して基本的に1つのメインスレッドを持ちます。このスレッドはJavaScript の実行・DOM の更新・レイアウト計算・ペイントをすべて担当します。重い処理が走るとこれらがブロックされ、UI がフリーズします。

人間が UI の「遅さ」を感じる閾値は約 100ms と言われています。一方、JavaScript で1万件のデータをソートすると数十〜数百ms かかります。50MB の CSV ファイルをパースすると1〜3秒ブロックされることもあります。Web Workers はこの問題を根本から解決する「マルチスレッド」の仕組みです。

>【メインスレッドがブロックされる典型的なケース】

❌ 大量データの処理(CSVパース・JSONの解析)
❌ 複雑な数値計算(物理シミュレーション・機械学習の推論)
❌ 画像処理(フィルター適用・リサイズ・カラー変換)
❌ 暗号化・ハッシュ計算
❌ 大きなデータの並び替え・検索
❌ Zip/Gzipの圧縮・解凍処理
❌ テキストの全文検索インデックス構築

→ これらをメインスレッドで実行すると:
  - スクロールが止まる
  - ボタンが反応しない
  - アニメーションがカクつく
  - Core Web Vitals(INP・LCP)が悪化する

Web Workers と Core Web Vitals の関係

指標説明Web Workers 導入前Web Workers 導入後
INP(Interaction to Next Paint)操作後の次のペイントまでの時間重い処理中はユーザー操作への応答が遅延(200ms 超)メインスレッドが空くため即座に応答(50ms 以下)
LCP(Largest Contentful Paint)最大コンテンツ描画時間起動時の重い処理が LCP をブロック起動時の重い処理を Worker に移して LCP 改善
TBT(Total Blocking Time)メインスレッドのブロック合計時間長い処理タスクが積み重なるWorker へのオフロードでタスク時間を 50ms 以下に

Google のランキングアルゴリズムには Core Web Vitals が直接影響します。特に INP(Interaction to Next Paint)は 2024年3月に FID に替わる新しい指標として正式採用されており、Web アプリの応答性改善は SEO にも直結します。


Web Workers の基本的な使い方|postMessage による通信

基本:Worker の作成とメッセージ通信

>// main.js - メインスレッド側
const worker = new Worker('./worker.js')

// Workerにメッセージを送る
worker.postMessage({ type: 'CALCULATE', data: [1, 2, 3, 4, 5] })

// Workerからの結果を受け取る
worker.addEventListener('message', (event) => {
  console.log('計算結果:', event.data)  // → { result: 15 }
})

// エラー処理
worker.addEventListener('error', (error) => {
  console.error('Worker エラー:', error.message)
})

// Workerを終了する(不要になったら必ず破棄する)
// worker.terminate()
>// worker.js - Workerスレッド側
// ⚠️ WorkerではDOMにアクセスできない(document/windowは使えない)
// ✅ fetch・IndexedDB・WebSocket・setTimeout等は使える

addEventListener('message', (event) => {
  const { type, data } = event.data

  switch (type) {
    case 'CALCULATE': {
      // 重い計算処理(メインスレッドをブロックしない)
      const result = data.reduce((sum, n) => sum + n, 0)
      // 結果をメインスレッドに返す
      postMessage({ type: 'RESULT', result })
      break
    }
    case 'SORT': {
      const sorted = [...data].sort((a, b) => a - b)
      postMessage({ type: 'SORTED', result: sorted })
      break
    }
  }
})

実践:大量 CSV データのパース処理をオフロード

>// csv-worker.js
addEventListener('message', (event) => {
  const { csvText } = event.data

  // 大量データのパース(メインスレッドをブロックしない)
  const lines = csvText.split('\n')
  const headers = lines[0].split(',')
  const records = lines.slice(1)
    .filter(line => line.trim())  // 空行を除外
    .map(line => {
      const values = line.split(',')
      return headers.reduce((obj, header, i) => {
        obj[header.trim()] = values[i]?.trim() ?? ''
        return obj
      }, {})
    })

  // 処理完了を通知
  postMessage({ records, count: records.length })
})
>// main.js - CSVファイルをWorkerで処理する
const csvWorker = new Worker('./csv-worker.js')

async function handleFileUpload(file) {
  const text = await file.text()

  // UIは継続して応答する(スピナーを表示しながら待てる)
  showLoadingSpinner()

  csvWorker.postMessage({ csvText: text })

  csvWorker.addEventListener('message', (event) => {
    hideLoadingSpinner()
    const { records, count } = event.data
    console.log(`${count}件のレコードをパースしました`)
    renderTable(records)
  }, { once: true })  // 一度だけ受け取ったらリスナーを削除
}

TransferableObjects|ゼロコピーで大きなデータを転送する

デフォルトの postMessage ではデータはコピーされて渡されます。100MB の画像データをコピー転送すると、メモリを 200MB 消費した上に転送時間がかかります。TransferableObjects を使うと所有権の移転(ゼロコピー)で高速かつメモリ効率よくデータを渡せます

転送方式仕組み速度元のオブジェクト
コピー転送(デフォルト)データを複製してから渡すデータサイズに比例して遅くなる元のオブジェクトはそのまま使える
Transferable(所有権移転)メモリの所有権ごと移転するデータサイズに関わらず高速移転後は元のオブジェクトは使えなくなる
SharedArrayBuffer(共有)メモリを共有する最速(コピーなし・移転なし)両方から参照・変更可能
>// transferable_example.js - TransferableObjectsの使い方

// ❌ 遅い:100MBのArrayBufferをコピーして転送(合計200MB使用)
const bigBuffer = new ArrayBuffer(100 * 1024 * 1024)  // 100MB
worker.postMessage({ buffer: bigBuffer })  // コピーされる

// ✅ 速い:ArrayBufferの所有権を移転(コピーなし、メモリ効率◎)
const bigBuffer2 = new ArrayBuffer(100 * 1024 * 1024)  // 100MB
worker.postMessage(
  { buffer: bigBuffer2 },
  [bigBuffer2]  // 第2引数にTransferリストを指定
)
// ⚠️ 移転後は bigBuffer2 は使えなくなる(detached状態)
console.log(bigBuffer2.byteLength)  // → 0(空になっている)

// Transferableとして使えるオブジェクト:
// - ArrayBuffer
// - MessagePort
// - ReadableStream / WritableStream / TransformStream
// - ImageBitmap
// - OffscreenCanvas

実践:ImageData を Transferable で高速転送

>// image-worker.js - 画像処理Workerでの双方向Transferable活用

addEventListener('message', (event) => {
  const { buffer, width, height, filter } = event.data
  const data = new Uint8ClampedArray(buffer)

  // フィルター処理
  if (filter === 'grayscale') {
    for (let i = 0; i < data.length; i += 4) {
      const avg = (data[i] + data[i+1] + data[i+2]) / 3
      data[i] = data[i+1] = data[i+2] = avg
    }
  }

  // 処理済みバッファを所有権移転で返す(コピーなし)
  postMessage(
    { buffer, width, height },
    [buffer]  // 所有権を移転して返す
  )
})
>// main.js - Canvas画像をWorkerで処理する高速版

const imageWorker = new Worker('./image-worker.js', { type: 'module' })

async function applyFilterFast(canvas, filter) {
  const ctx = canvas.getContext('2d')
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)

  // ImageData.data(Uint8ClampedArray)のbufferをTransferableで送る
  const buffer = imageData.data.buffer

  return new Promise((resolve) => {
    imageWorker.addEventListener('message', (e) => {
      const resultData = new Uint8ClampedArray(e.data.buffer)
      const resultImageData = new ImageData(resultData, canvas.width, canvas.height)
      ctx.putImageData(resultImageData, 0, 0)
      resolve()
    }, { once: true })

    imageWorker.postMessage(
      { buffer, width: canvas.width, height: canvas.height, filter },
      [buffer]  // バッファの所有権を移転
    )
  })
}

Comlink:postMessage をもっと簡単にする

生の postMessage API は、複数の処理タイプを扱うとコードが複雑になります。Comlink ライブラリを使うと、Worker の関数を通常の非同期関数のように呼び出せます。

>npm install comlink
>// heavy-worker.ts - Comlink で Worker の関数を公開する
import { expose } from 'comlink'

const api = {
  // 重い画像フィルター処理
  async applyGrayscale(imageData: ImageData): Promise<ImageData> {
    const data = imageData.data
    for (let i = 0; i < data.length; i += 4) {
      const avg = (data[i] + data[i+1] + data[i+2]) / 3
      data[i] = data[i+1] = data[i+2] = avg
    }
    return imageData
  },

  // 大規模なソート処理
  async sortLargeArray(arr: number[]): Promise<number[]> {
    return arr.sort((a, b) => a - b)
  },

  // ハッシュ計算(Web Crypto API はWorkerでも使える)
  async computeHash(text: string): Promise<string> {
    const encoder = new TextEncoder()
    const data = encoder.encode(text)
    const hashBuffer = await crypto.subtle.digest('SHA-256', data)
    const hashArray = Array.from(new Uint8Array(hashBuffer))
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
  },

  // プログレス付き重い処理(コールバックを渡す例)
  async processWithProgress(
    data: number[],
    onProgress: (percent: number) => void
  ): Promise<number[]> {
    const result = []
    for (let i = 0; i < data.length; i++) {
      result.push(data[i] * 2)  // 重い処理のシミュレーション
      if (i % 1000 === 0) {
        onProgress(Math.floor((i / data.length) * 100))
      }
    }
    return result
  }
}

expose(api)
>// main.ts - Comlink で Worker を通常の関数のように呼び出す
import { wrap, proxy } from 'comlink'
import type { Remote } from 'comlink'

// Worker の型
type WorkerAPI = typeof import('./heavy-worker').api

const worker = new Worker(new URL('./heavy-worker.ts', import.meta.url), {
  type: 'module'
})
const api: Remote<WorkerAPI> = wrap<WorkerAPI>(worker)

// async/await で Worker の関数を直接呼び出せる
async function processImage(canvas: HTMLCanvasElement) {
  const ctx = canvas.getContext('2d')!
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)

  // Worker に処理をオフロード(UI はブロックされない)
  const processed = await api.applyGrayscale(imageData)
  ctx.putImageData(processed, 0, 0)
}

// コールバック付き処理(Comlink の proxy() を使う)
async function processWithProgress(data: number[]) {
  const result = await api.processWithProgress(
    data,
    proxy((percent: number) => {
      console.log(`進捗: ${percent}%`)
      updateProgressBar(percent)
    })
  )
  return result
}

Worker Pool|複数 Worker で並列処理する

CPU のコア数に合わせて複数の Worker を起動し、タスクを並列処理する「Worker Pool」パターンを使うと、大量のタスクをより高速に処理できます。例えば画像変換ツールで100枚の画像を一括処理する場合、4コア CPU に4つの Worker を起動すれば理論上4倍速くなります。

>// worker-pool.ts - シンプルなWorker Pool実装

class WorkerPool {
  private workers: Worker[]
  private taskQueue: Array<{
    data: unknown
    resolve: (result: unknown) => void
    reject: (error: unknown) => void
  }> = []
  private idleWorkers: Worker[]

  constructor(workerUrl: URL, poolSize: number = navigator.hardwareConcurrency) {
    // CPUのコア数に合わせてWorkerを起動(デフォルト)
    this.workers = Array.from({ length: poolSize }, () =>
      new Worker(workerUrl, { type: 'module' })
    )
    this.idleWorkers = [...this.workers]

    console.log(`Worker Pool: ${poolSize}スレッドで起動しました`)
  }

  async execute(data: unknown): Promise<unknown> {
    return new Promise((resolve, reject) => {
      const task = { data, resolve, reject }

      const idleWorker = this.idleWorkers.pop()
      if (idleWorker) {
        this.runTask(idleWorker, task)
      } else {
        // 空きWorkerがなければキューに追加
        this.taskQueue.push(task)
      }
    })
  }

  private runTask(worker: Worker, task: typeof this.taskQueue[0]) {
    const handler = (event: MessageEvent) => {
      worker.removeEventListener('message', handler)
      worker.removeEventListener('error', errorHandler)

      task.resolve(event.data)

      // 次のタスクを取り出して実行
      const nextTask = this.taskQueue.shift()
      if (nextTask) {
        this.runTask(worker, nextTask)
      } else {
        this.idleWorkers.push(worker)
      }
    }

    const errorHandler = (error: ErrorEvent) => {
      worker.removeEventListener('message', handler)
      worker.removeEventListener('error', errorHandler)
      task.reject(error)
      this.idleWorkers.push(worker)
    }

    worker.addEventListener('message', handler)
    worker.addEventListener('error', errorHandler)
    worker.postMessage(task.data)
  }

  terminate() {
    this.workers.forEach(w => w.terminate())
  }
}

// 使用例:100枚の画像を並列処理
const pool = new WorkerPool(new URL('./image-worker.ts', import.meta.url))

const images = getImageDataArray()  // 100個のImageData
const results = await Promise.all(
  images.map(img => pool.execute({ imageData: img, filter: 'grayscale' }))
)
console.log(`${results.length}枚の画像処理完了`)
pool.terminate()

React / Next.js での実装パターン

カスタム Hook で Worker を管理する

>// hooks/useWorker.ts - Web WorkerをReact Hooksで管理する

import { useEffect, useRef, useState, useCallback } from 'react'

type WorkerState<T> = {
  result: T | null
  loading: boolean
  error: string | null
}

export function useWorker<T = unknown>(workerUrl: URL) {
  const workerRef = useRef<Worker | null>(null)
  const [state, setState] = useState<WorkerState<T>>({
    result: null,
    loading: false,
    error: null,
  })

  useEffect(() => {
    workerRef.current = new Worker(workerUrl, { type: 'module' })

    workerRef.current.addEventListener('message', (event: MessageEvent<T>) => {
      setState({ result: event.data, loading: false, error: null })
    })

    workerRef.current.addEventListener('error', (error: ErrorEvent) => {
      setState(prev => ({ ...prev, loading: false, error: error.message }))
    })

    return () => {
      workerRef.current?.terminate()
    }
  }, [workerUrl])

  const execute = useCallback((data: unknown) => {
    if (!workerRef.current) return
    setState(prev => ({ ...prev, loading: true, error: null }))
    workerRef.current.postMessage(data)
  }, [])

  return { ...state, execute }
}
>// components/DataProcessor.tsx - カスタムHookを使うコンポーネント
'use client'

import { useWorker } from '@/hooks/useWorker'
import { useState } from 'react'

type SortResult = { sorted: number[]; time: number }

export function DataProcessor() {
  const [inputText, setInputText] = useState('')
  const { result, loading, error, execute } = useWorker<SortResult>(
    new URL('../workers/sort-worker.ts', import.meta.url)
  )

  const handleProcess = () => {
    const numbers = inputText
      .split(',')
      .map(Number)
      .filter(n => !isNaN(n))
    execute({ numbers })
  }

  return (
    <div className="p-4 space-y-4">
      <textarea
        value={inputText}
        onChange={(e) => setInputText(e.target.value)}
        placeholder="カンマ区切りで数値を入力(例: 3,1,4,1,5,9,2,6)"
        className="w-full border rounded p-2 h-24"
      />
      <button
        onClick={handleProcess}
        disabled={loading || !inputText}
        className="bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50"
      >
        {loading ? 'ソート中...' : 'Workerでソート'}
      </button>

      {error && <p className="text-red-500">{error}</p>}

      {result && (
        <div className="bg-gray-50 p-3 rounded">
          <p className="text-sm text-gray-500">処理時間: {result.time}ms</p>
          <p className="mt-1">{result.sorted.slice(0, 20).join(', ')}...</p>
        </div>
      )}
    </div>
  )
}

Vite での Worker インポート設定

>// Vite(Next.js / React)での Worker ファイルのインポート方法

// ✅ Vite 推奨の書き方(import.meta.url を使う)
const worker = new Worker(new URL('./heavy-worker.ts', import.meta.url), {
  type: 'module'
})

// ✅ Vite の ?worker サフィックスを使う書き方
import HeavyWorker from './heavy-worker.ts?worker'
const worker = new HeavyWorker()

// ✅ Next.js での書き方(package.json の type: "module" が必要)
// next.config.ts で worker を設定
// module.exports = { experimental: { workerThreads: true } }

OMT アーキテクチャ|UI とロジックを完全分離する設計

Off-Main-Thread(OMT)アーキテクチャとは、UI の状態管理・ビジネスロジックを Worker に移し、メインスレッドはレンダリングのみに専念させる設計思想です。Google が開発したゲーム「PROXX」で効果が実証されました。

>【OMTアーキテクチャの分担】

メインスレッド(UIスレッド):
→ DOMの更新・アニメーション・CSS適用
→ ユーザー操作のイベント受信
→ Workerから受け取った状態変化をレンダリング
→ 担当するJavaScriptは最小限に抑える

Workerスレッド:
→ アプリケーション状態管理(Redux相当)
→ ビジネスロジック・計算
→ データの取得・変換・バリデーション
→ 状態変化をメインスレッドに通知

メリット:
✅ ローエンドデバイス(Android エントリーモデル等)でもUIが滑らかになる
✅ ロジックがDOMから分離されてユニットテストが書きやすくなる
✅ 将来的にマルチコアへのスケールアウトが容易
✅ UIとロジックの関心事を明確に分離できる

注意点:
→ Worker-メインスレッド間の通信オーバーヘッドが発生する
→ DOM操作は依然メインスレッドのみ可能
→ 初期実装コストがやや高い(設計を丁寧に考える必要がある)

OMT の具体的な実装例

>// state-worker.ts - OMTパターンでのステート管理Worker

type State = {
  items: Item[]
  filter: string
  sortBy: string
}

type Action =
  | { type: 'SET_FILTER'; filter: string }
  | { type: 'SET_SORT'; sortBy: string }
  | { type: 'LOAD_ITEMS'; items: Item[] }

let state: State = {
  items: [],
  filter: '',
  sortBy: 'name'
}

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'SET_FILTER':
      return { ...state, filter: action.filter }
    case 'SET_SORT':
      return { ...state, sortBy: action.sortBy }
    case 'LOAD_ITEMS':
      return { ...state, items: action.items }
    default:
      return state
  }
}

function getFilteredSortedItems(state: State) {
  return state.items
    .filter(item =>
      item.name.toLowerCase().includes(state.filter.toLowerCase())
    )
    .sort((a, b) =>
      a[state.sortBy as keyof Item] > b[state.sortBy as keyof Item] ? 1 : -1
    )
}

addEventListener('message', (event) => {
  const action: Action = event.data

  // 状態を更新
  state = reducer(state, action)

  // フィルター・ソート済みのアイテムを計算(重い処理)
  const displayItems = getFilteredSortedItems(state)

  // 差分のみメインスレッドに送る
  postMessage({ type: 'STATE_UPDATE', displayItems })
})

パフォーマンス計測と最適化のヒント

Chrome DevTools で Worker のパフォーマンスを計測する

>【Chrome DevTools でのWorkerパフォーマンス計測手順】

1. DevToolsを開く(F12)
2. Performanceタブを選択
3. 「Record」ボタンを押して処理を実行
4. 「Stop」で記録終了

確認ポイント:
- 「Main」スレッドの長いタスク(赤くマークされる50ms超のタスク)
- 「Worker」スレッドの処理時間
- postMessage の通信頻度とサイズ

最適化の指標:
- メインスレッドのタスクを 50ms 以下に抑える
- Workerへの postMessage は頻繁すぎない(バッチ処理を検討)
- 大きなデータはTransferableを使う

Worker 通信のバッチ化

>// 頻繁すぎるpostMessageはオーバーヘッドになる
// バッチ処理で通信回数を減らす

// ❌ 悪い例:毎フレームworkerに送信(60fps = 1秒60回)
function onFrameUpdate(data) {
  worker.postMessage({ type: 'UPDATE', data })  // 1秒に60回送信
}

// ✅ 良い例:データを溜めてまとめて送信(通信回数を削減)
const pendingData: unknown[] = []

function onFrameUpdate(data) {
  pendingData.push(data)
}

// 100ms ごとにバッチ送信
setInterval(() => {
  if (pendingData.length === 0) return
  worker.postMessage({ type: 'BATCH_UPDATE', items: [...pendingData] })
  pendingData.length = 0
}, 100)

よくある質問

Service Worker と Web Worker の違いは何ですか?

Web Worker は「重い処理をメインスレッドからオフロードする」目的で使います。ページのライフサイクルに連動し、ページを閉じると終了します。Service Worker はネットワークリクエストのプロキシ・オフラインキャッシュ・プッシュ通知の受信に使うもので、ページを閉じても登録は維持されます(PWA の核心技術)。名前が似ていますが全く別の技術です。

Worker 間で localStorage は共有できますか?

Worker から localStorage にアクセスできません。Worker から永続的なデータにアクセスするには IndexedDB を使います(Worker から IndexedDB は利用可能)。また SharedArrayBuffer を使ってメインスレッドと Worker で共有メモリを使うことも可能ですが、COOP/COEP ヘッダーの設定が必要です。

Vite や Webpack で Web Worker を使うにはどうすればいいですか?

Vite では new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' }) の形式で Worker ファイルを指定するだけで動作します。TypeScript も自動的にトランスパイルされます。Webpack では worker-loader プラグインを導入するか、Webpack 5 以降では同様の形式がネイティブサポートされています。

Web Workers を使うとデバッグが難しくなりませんか?

Chrome DevTools は Worker のデバッグに対応しています。Sources タブの左サイドバーに「Workers」というセクションがあり、Worker スクリプトにブレークポイントを設置できます。console.log も Worker 内で使え、コンソールには「[Worker]」というプレフィックスが付いて表示されます。

Worker 内で fetch を使って API を叩けますか?

使えます。Worker 内では fetchsetTimeoutsetIntervalWebSocketIndexedDBcrypto などの API を利用できます。DOM 操作(documentwindow のプロパティへのアクセス)のみ制限されています。


まとめ

  • メインスレッドのブロック:重い処理が UI のフリーズ・INP/LCP の悪化を引き起こす。Web Workers でオフロードして Core Web Vitals を改善できる
  • postMessage:メインスレッドと Worker 間の通信手段。デフォルトはコピー転送
  • TransferableObjects:ArrayBuffer 等を所有権移転でゼロコピー転送。大きなデータの転送速度を劇的に改善できる
  • Comlink:postMessage をラップして Worker の API を通常の非同期関数のように呼び出せるライブラリ。コールバック(proxy)にも対応
  • Worker Pool:CPU コア数に合わせて複数 Worker を起動し、タスクを並列処理するパターン。大量タスクの処理速度を最大化できる
  • カスタム HookuseWorker フックで React コンポーネントから Worker を宣言的に使える
  • OMT アーキテクチャ:UI とロジックを分離して Worker に移すことで、あらゆるデバイスで安定したパフォーマンスを実現できる

Web Workers は「パフォーマンスが問題になってから対処する」のではなく、設計段階から取り込むことで恩恵が最大化します。まず重い処理を1つ Worker に移してみることから始めましょう。TransferableObjects と Comlink を組み合わせることで、実装コストを低く保ちながら高いパフォーマンスを実現できます。


関連記事


参考リンク(公式・一次情報)


AIスキルで、未来の自分をアップデート!

今なら完全無料でAIを学べる!WithAI

今なら完全無料でAIを学べる!

  • 動画や実践で楽しく学べる:初心者でも安心のカリキュラム
  • スマホ・PCどちらでもOK:好きな時間に学習できる
  • 料金は一切ナシ0円でAIスキルが身につく

目的に合わせて選べる「AI副業」「AI転職」「AI活用」の3コースを用意。副収入・キャリアチェンジ・日常の生産性アップまで、あなたのゴールに合わせてAIを学べます。

会員登録はカンタン30秒で完了します。まずは公式LINEから、無料でAI学習をスタートしましょう!

この記事を書いた人

WithCode(ウィズコード)は「目指すなら稼げる人材」をビジョンに、累計400名以上のフリーランスを輩出してきた超実践型プログラミングスクールです。150社以上の実案件支援を特徴にWeb制作・Webデザインなどの役立つ情報を現場のノウハウに基づいて発信していきます。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次