Fast JavaScript ListView 2026: เทคนิค Windowing, Virtualization และการเพิ่มประสิทธิภาพ
เชี่ยวชาญเทคนิค virtualization รายการ JavaScript ด้วย TanStack Virtual, react-virtuoso และ react-window เรียนรู้ windowing เพื่อ render 100,000+ รายการที่ 60fps พร้อมตัวอย่างโค้ด

การ render รายการหลายพันรายการใน JavaScript ListView โดยไม่มี virtualization ทำให้เบราว์เซอร์สร้าง DOM node หลายพัน node ส่งผลให้เวลา render เริ่มต้นช้า การ scroll กระตุก และการใช้หน่วยความจำที่อาจทำให้อุปกรณ์มือถือค้าง การใช้งาน Fast JavaScript ListView ในปี 2026 แก้ปัญหานี้ผ่าน windowing: render เฉพาะ 10-20 รายการที่มองเห็นใน viewport ในขณะที่จำลองพื้นที่ scrollable ที่มีรายการทั้งหมด
Windowing render เฉพาะรายการที่มองเห็นบวกกับ buffer เล็กๆ (overscan) ลด DOM node จาก 10,000 เหลือประมาณ 20 โดยไม่คำนึงถึงขนาดของรายการ
ทำไมประสิทธิภาพเบราว์เซอร์ลดลงเมื่อมีรายการจำนวนมาก
ทุก DOM node ใช้หน่วยความจำและต้องการให้เบราว์เซอร์ติดตามตำแหน่งของมันระหว่างการคำนวณ layout รายการ 10,000 รายการที่มี markup ซับซ้อนปานกลาง (avatar, text span สองอัน, ปุ่มหนึ่งปุ่ม) สร้าง DOM node มากกว่า 40,000 node เอนจิน rendering ของ Chrome คำนวณ style และตำแหน่งใหม่สำหรับทุก node ในทุก event การ scroll
ผลลัพธ์: render เริ่มต้นใช้เวลา 2-4 วินาที การ scroll ลดลงเหลือ 15fps และการใช้หน่วยความจำเพิ่มขึ้นถึง 500MB+ บน mobile Safari แท็บมักจะ crash ทันที
Virtualization แก้ไขปัญหานี้โดยการรักษาจำนวน DOM node ให้คงที่ ไม่ว่า array ข้อมูลจะมี 100 หรือ 100,000 รายการ DOM ที่ render จะอยู่ที่ประมาณ 20-30 node รักษาหน่วยความจำให้คงที่และประสิทธิภาพ scroll ที่ 60fps
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
interface Item {
id: string
name: string
email: string
}
export function VirtualizedList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null)
// Create virtualizer instance with estimated row height
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 60, // estimated height in pixels
overscan: 5, // render 5 extra items above/below viewport
})
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualRow) => {
const item = items[virtualRow.index]
return (
<div
key={item.id}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
<div className="flex items-center gap-3 p-3 border-b">
<div className="w-10 h-10 rounded-full bg-gray-200" />
<div>
<p className="font-medium">{item.name}</p>
<p className="text-sm text-gray-500">{item.email}</p>
</div>
</div>
</div>
)
})}
</div>
</div>
)
}การใช้งานนี้ render เฉพาะแถวที่มองเห็นบวกกับ buffer overscan container ด้านนอกกำหนดความสูง scrollable ทั้งหมด ทำให้ scrollbar ทำงานเหมือนว่ารายการทั้งหมดมีอยู่ใน DOM
TanStack Virtual: แนวทาง Headless สำหรับปี 2026
TanStack Virtual ให้ primitive virtualization ที่ไม่ขึ้นกับ framework ต่างจาก react-window หรือ react-virtuoso ไลบรารีนี้ไม่ให้ component สำเร็จรูป มีแค่ hooks ที่คำนวณว่ารายการไหนควร render และตำแหน่งของมัน สถาปัตยกรรม headless นี้หมายถึงการควบคุม styling และโครงสร้าง DOM อย่างสมบูรณ์
เวอร์ชันปัจจุบัน (3.13) รองรับ React, Vue, Solid, Svelte และ vanilla JavaScript ตัวเลือกการกำหนดค่าหลัก:
| ตัวเลือก | วัตถุประสงค์ | ค่าเริ่มต้น |
|---|---|---|
count | จำนวนรายการทั้งหมด | required |
estimateSize | ฟังก์ชันที่คืนค่าความสูง/กว้างโดยประมาณของรายการ | required |
overscan | รายการเพิ่มเติมที่ render นอก viewport | 1 |
getScrollElement | คืนค่า container ที่ scrollable | required |
measureElement | ฟังก์ชันการวัดแบบกำหนดเองสำหรับขนาดแบบไดนามิก | auto |
ค่า overscan ส่งผลโดยตรงต่อความลื่นของการ scroll การตั้งค่าต่ำเกินไป (0-2) ทำให้เกิด flash สีขาวระหว่างการ scroll เร็ว การตั้งค่าสูงเกินไป (20+) ทำให้ประโยชน์ของ virtualization หายไป สำหรับแอปพลิเคชันส่วนใหญ่ 5-10 ให้ความสมดุลที่เหมาะสม
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef, useCallback } from 'react'
interface Message {
id: string
content: string
timestamp: number
}
export function ChatList({ messages }: { messages: Message[] }) {
const parentRef = useRef<HTMLDivElement>(null)
// measureElement enables dynamic height measurement
const virtualizer = useVirtualizer({
count: messages.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80, // rough estimate, actual measured later
overscan: 8,
})
return (
<div ref={parentRef} style={{ height: '100vh', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualRow) => {
const message = messages[virtualRow.index]
return (
<div
key={message.id}
data-index={virtualRow.index}
ref={virtualizer.measureElement} // enables auto-measurement
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
}}
>
<div className="p-4 border-b">
<p className="text-sm text-gray-500">
{new Date(message.timestamp).toLocaleTimeString()}
</p>
<p className="mt-1">{message.content}</p>
</div>
</div>
)
})}
</div>
</div>
)
}Ref measureElement ทำให้สามารถวัดความสูงอัตโนมัติหลังจาก render ซึ่งจัดการกับความยาวเนื้อหาที่แตกต่างกันโดยไม่ต้องคำนวณความสูงด้วยตนเอง
react-virtuoso: ทางเลือกที่มีฟีเจอร์ครบครัน
react-virtuoso ใช้แนวทางตรงข้าม: ไลบรารี component แบบ batteries-included ในขณะที่ TanStack Virtual ต้องการสร้าง scroll container และ logic การจัดตำแหน่ง react-virtuoso จัดการสิ่งเหล่านี้ภายใน
การแลกเปลี่ยนคือขนาด bundle (37KB เทียบกับ 12KB สำหรับ TanStack Virtual) แต่ boilerplate น้อยกว่ามากสำหรับ pattern ทั่วไปเช่น grouped list, sticky header และ interface chat ที่มี reverse scrolling
import { Virtuoso } from 'react-virtuoso'
interface User {
id: string
name: string
department: string
}
export function UserDirectory({ users }: { users: User[] }) {
return (
<Virtuoso
style={{ height: '600px' }}
data={users}
itemContent={(index, user) => (
<div className="flex items-center gap-3 p-4 border-b hover:bg-gray-50">
<div className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center">
{user.name.charAt(0)}
</div>
<div>
<p className="font-medium">{user.name}</p>
<p className="text-sm text-gray-500">{user.department}</p>
</div>
</div>
)}
/>
)
}นี่ให้ผลลัพธ์ virtualization เดียวกันด้วยโค้ดที่น้อยกว่า react-virtuoso วัดรายการอัตโนมัติ จัดการความสูงแบบไดนามิก และให้ persistence ตำแหน่ง scroll
สำหรับการเตรียมตัวสัมภาษณ์เกี่ยวกับประสิทธิภาพ React การเข้าใจว่าเมื่อไหร่ควรเลือกแต่ละไลบรารีแสดงให้เห็นความตระหนักด้านสถาปัตยกรรม TanStack Virtual เหมาะกับแอปพลิเคชันที่ใช้ TanStack Table อยู่แล้วหรือต้องการโค้ดที่ไม่ขึ้นกับ framework react-virtuoso เหมาะกับทีมที่ให้ความสำคัญกับความเร็วในการพัฒนามากกว่าขนาด bundle
พร้อมที่จะพิชิตการสัมภาษณ์ Node.js / NestJS แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
ปัญหาที่พบบ่อยและวิธีแก้ไข
ตำแหน่ง Scroll กระโดดเมื่อมีรูปภาพ
รูปภาพโหลดแบบ asynchronous เปลี่ยนความสูงแถวหลังจากการวัดครั้งแรก Virtualizer ไม่สามารถทำนายการเปลี่ยนแปลงนี้ได้ ทำให้เนื้อหาด้านล่างเลื่อน
วิธีแก้ไข: จองพื้นที่สำหรับรูปภาพโดยใช้ aspect-ratio หรือขนาดคงที่
interface ImageItem {
id: string
src: string
title: string
}
export function ImageListItem({ item }: { item: ImageItem }) {
return (
<div className="flex gap-4 p-4 border-b">
{/* Reserve 80x80px space regardless of load state */}
<div className="w-20 h-20 flex-shrink-0 bg-gray-100">
<img
src={item.src}
alt={item.title}
className="w-full h-full object-cover"
loading="lazy"
/>
</div>
<p className="font-medium">{item.title}</p>
</div>
)
}State หายไประหว่าง Scroll
รายการที่ virtualize unmount เมื่อ scroll ออกจากมุมมอง state local ใดๆ (input ฟอร์ม, accordion ที่ขยาย) หายไป
วิธีแก้ไข: ยก state ขึ้นไปที่ component parent หรือไลบรารี state management โดย key ตาม ID ของรายการ
import { useState, useCallback } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
interface FAQ {
id: string
question: string
answer: string
}
export function FAQList({ faqs }: { faqs: FAQ[] }) {
// State lives in parent, survives row unmounting
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
const parentRef = useRef<HTMLDivElement>(null)
const toggleExpanded = useCallback((id: string) => {
setExpandedIds(prev => {
const next = new Set(prev)
if (next.has(id)) {
next.delete(id)
} else {
next.add(id)
}
return next
})
}, [])
const virtualizer = useVirtualizer({
count: faqs.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 60,
overscan: 5,
})
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualRow) => {
const faq = faqs[virtualRow.index]
const isExpanded = expandedIds.has(faq.id)
return (
<div
key={faq.id}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
}}
>
<button
onClick={() => toggleExpanded(faq.id)}
className="w-full text-left p-4 border-b"
>
<p className="font-medium">{faq.question}</p>
{isExpanded && (
<p className="mt-2 text-gray-600">{faq.answer}</p>
)}
</button>
</div>
)
})}
</div>
</div>
)
}Key ไม่คงที่ทำให้เกิด Re-render
การใช้ index ของ array เป็น key ใช้ได้จนกว่ารายการจะเรียงลำดับใหม่ รายการได้รับ state ผิด ทำให้เกิด glitch visual และ render ที่สูญเปล่า
ใช้ identifier ที่ไม่ซ้ำและคงที่จากข้อมูลเองเสมอ:
// Correct: stable key from data
{virtualizer.getVirtualItems().map((virtualRow) => {
const item = items[virtualRow.index]
return <div key={item.id}>...</div> // id from database/API
})}
// Wrong: index changes when list reorders
{virtualizer.getVirtualItems().map((virtualRow, index) => {
return <div key={index}>...</div> // causes bugs on reorder
})}การเปรียบเทียบไลบรารีสำหรับโปรเจกต์ 2026
การเลือกไลบรารี virtualization ที่เหมาะสมขึ้นอยู่กับความต้องการของโปรเจกต์ การเปรียบเทียบนี้สะท้อน ecosystem ปี 2026:
| เกณฑ์ | TanStack Virtual | react-virtuoso | react-window |
|---|---|---|---|
| ขนาด bundle | 12KB | 37KB | 6KB |
| ความสูงแบบไดนามิก | ตั้งค่าด้วยตนเอง | อัตโนมัติ | ไม่รองรับ |
| TypeScript | Native | Native | package @types |
| รองรับ framework | React, Vue, Solid, Svelte | React เท่านั้น | React เท่านั้น |
| การบำรุงรักษา | Active (2026) | Active (2026) | Maintenance mode |
| ความยากในการเรียนรู้ | สูงกว่า | ต่ำกว่า | ต่ำสุด |
react-window ยังคงใช้ได้สำหรับรายการ fixed-height อย่างง่ายที่ขนาด bundle สำคัญ อย่างไรก็ตาม สถานะ maintenance-mode และการไม่รองรับความสูงแบบไดนามิกทำให้ไม่เหมาะสำหรับโปรเจกต์ใหม่ที่มีความต้องการซับซ้อน
สำหรับการสร้างแอปพลิเคชัน React ที่มีประสิทธิภาพ การเข้าใจ tradeoff ของ virtualization แสดงให้เห็นความตระหนักด้านประสิทธิภาพที่ผู้สัมภาษณ์มองหา
การวัดการปรับปรุงประสิทธิภาพ
การวัดประโยชน์ของ virtualization ต้องการวิธีการวัดที่สม่ำเสมอ Performance panel ของ Chrome DevTools ให้ metrics ที่เกี่ยวข้อง
การวัดหลักก่อน/หลัง virtualization:
- First Contentful Paint (FCP): เวลาจนกว่าเนื้อหาแรกจะ render Virtualization ลดสิ่งนี้โดยจำกัดการสร้าง DOM เริ่มต้น
- Total Blocking Time (TBT): การ block main thread ระหว่างการโหลด DOM node น้อยลงหมายถึงการคำนวณ style/layout น้อยลง
- การใช้หน่วยความจำ: Snapshot heap size ใน Memory panel ควรคงที่โดยไม่คำนึงถึงความยาวรายการ
- Scroll fps: บันทึก Performance trace ขณะ scrolling Frame rate ควรอยู่ที่ 60fps
import { useEffect, useRef } from 'react'
export function useScrollFPS(containerRef: React.RefObject<HTMLElement>) {
const frameCountRef = useRef(0)
const lastTimeRef = useRef(performance.now())
useEffect(() => {
const container = containerRef.current
if (!container) return
let rafId: number
const measureFrame = () => {
frameCountRef.current++
const now = performance.now()
if (now - lastTimeRef.current >= 1000) {
console.log(`Scroll FPS: ${frameCountRef.current}`)
frameCountRef.current = 0
lastTimeRef.current = now
}
rafId = requestAnimationFrame(measureFrame)
}
const handleScroll = () => {
if (!rafId) {
rafId = requestAnimationFrame(measureFrame)
}
}
container.addEventListener('scroll', handleScroll, { passive: true })
return () => {
container.removeEventListener('scroll', handleScroll)
cancelAnimationFrame(rafId)
}
}, [containerRef])
}Hook นี้บันทึก scroll FPS ระหว่างการพัฒนา แอปพลิเคชัน production ควรใช้เครื่องมือ observability เช่น Sentry Performance หรือ telemetry แบบกำหนดเอง
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
ประเด็นสำคัญสำหรับประสิทธิภาพรายการ JavaScript
- Virtualization ลด DOM node จากหลายพันเหลือประมาณ 20 รักษาหน่วยความจำและประสิทธิภาพ scroll คงที่โดยไม่คำนึงถึงขนาดข้อมูล
- TanStack Virtual (12KB) ให้ primitive แบบ headless สำหรับการควบคุมสูงสุดบน React, Vue, Solid และ Svelte
- react-virtuoso (37KB) ให้ component แบบ batteries-included พร้อมการจัดการความสูงแบบไดนามิกอัตโนมัติ
- react-window ยังคงอยู่ใน maintenance mode ตั้งแต่ 2022 และไม่รองรับความสูงแบบไดนามิก
- ตั้ง overscan ระหว่าง 5-10 สำหรับความลื่นของ scroll ที่เหมาะสมโดยไม่ทำให้ประโยชน์ของ virtualization หายไป
- จองขนาดรูปภาพเพื่อป้องกันการกระโดดตำแหน่ง scroll ระหว่างการโหลดแบบ asynchronous
- ยก state ที่ expandable/editable ขึ้นไปที่ component parent เพราะแถวที่ virtualize unmount ระหว่าง scroll
- ใช้ key ที่คงที่จาก ID ข้อมูล ไม่ใช่ index ของ array เพื่อป้องกัน state ไม่ตรงกันเมื่อเรียงลำดับใหม่
- วัด FCP, TBT, หน่วยความจำ และ scroll FPS ก่อน/หลังการใช้งานเพื่อวัดการปรับปรุง
คุณหาบั๊กใน Node.js / NestJS เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 15 กันยายน 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

High Performance JavaScript ListView 2026: เทคนิค Virtualization และการเพิ่มประสิทธิภาพ
คู่มือฉบับสมบูรณ์เกี่ยวกับเทคนิค virtualization สำหรับ list ใน JavaScript เพื่อประสิทธิภาพสูงในปี 2026 เรียนรู้ windowing, lazy loading และการเพิ่มประสิทธิภาพ rendering สำหรับแอปพลิเคชันขนาดใหญ่

Microservices ด้วย NestJS ปี 2026: สถาปัตยกรรม, gRPC และคำถามสัมภาษณ์งาน
คู่มือฉบับสมบูรณ์เรื่องสถาปัตยกรรม NestJS Microservices กับ gRPC: transport layer, Protocol Buffers, streaming patterns และคำถามสัมภาษณ์งานสำหรับ backend engineer ปี 2026

NestJS + Prisma: สแตกแบ็กเอนด์สมัยใหม่สำหรับ Node.js
คู่มือฉบับสมบูรณ์ในการสร้าง API แบ็กเอนด์สมัยใหม่ด้วย NestJS และ Prisma ครอบคลุมการตั้งค่า โมเดล เซอร์วิส ทรานแซกชัน และแนวปฏิบัติที่ดี