Skip to content

Тяжёлая сортировка без просадки UI: Worker Kit + Virtual Scroller Kit

Список на 100 000+ строк сам по себе не проблема — Virtual Scroller Kit рендерит только видимые строки. Проблема начинается, когда список ещё и сортируется или фильтруется на каждое изменение: Array.prototype.sort на массиве такого размера — это синхронная, блокирующая UI работа на основном потоке, и async/await её не спасает — сам цикл сортировки всё равно выполняется в том же потоке, что рисует интерфейс.

Worker Kit выносит саму сортировку в честный Worker-поток, а VirtualList рендерит уже готовый результат.

Воркер

ts
// sort-rows.worker.ts
import { defineWorkerHandler } from 'vue-worker-kit/worker'

interface Row {
  id: number
  text: string
  score: number
}

export default defineWorkerHandler(async (data: Row[], ctx) => {
  return data.sort((a, b) => b.score - a.score)
})

Компонент

vue
<script setup lang="ts">
import { ref } from 'vue'
import { useWorker } from 'vue-worker-kit'
import { VirtualList } from 'vue-virtual-scroller-kit'

interface Row {
  id: number
  text: string
  score: number
}

const { run, isRunning } = useWorker<typeof import('./sort-rows.worker')>(
  () => new Worker(new URL('./sort-rows.worker.ts', import.meta.url), { type: 'module' }),
)

const rows = ref<Row[]>([])

async function resort(source: Row[]) {
  // sorted: Row[] — тип выведен из sort-rows.worker.ts, дженерик не нужен
  rows.value = await run(source)
}
</script>

<template>
  <p v-if="isRunning">Сортируем…</p>
  <VirtualList :items="rows" :estimated-item-size="48" style="height: 600px">
    <template #default="{ item }">
      <div style="padding: 12px 16px; border-bottom: 1px solid #eee">{{ item.text }}</div>
    </template>
  </VirtualList>
</template>

Почему это разумно именно вместе, а не отдельно

VirtualList сам по себе не ускоряет сортировку — он ускоряет только рендер. Без воркера интерфейс всё равно "подвиснет" на время sort(), просто список после этого будет длинным, но быстрым. Воркер сам по себе тоже не обязателен для маленьких списков — цена postMessage и структурного клонирования данных туда-обратно реальна, и для списка в 500 строк обычная сортировка на основном потоке отработает быстрее. Пара имеет смысл именно на масштабе, где проблема реальна: десятки-сотни тысяч строк, которые к тому же пересортировываются часто (по клику на заголовок колонки, при вводе в поиск).

Что дальше

  • Для по-настоящему больших наборов, обрабатываемых пачками, — createWorkerPool() вместо одного useWorker(). См. Worker Kit — обзор.
  • Для табличного, а не просто построчного вида — VirtualTable вместо VirtualList, тот же принцип. См. Virtual Scroller Kit — обзор.