This commit is contained in:
Mackie 2026-06-07 22:56:36 +08:00
parent 1539464396
commit 1ed574680f
6 changed files with 100 additions and 121 deletions

View file

@ -10,6 +10,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@tanstack/react-virtual": "^3.14.2",
"react": "^19.2.6", "react": "^19.2.6",
"react-dom": "^19.2.6" "react-dom": "^19.2.6"
}, },

20
pnpm-lock.yaml generated
View file

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@tanstack/react-virtual':
specifier: ^3.14.2
version: 3.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
react: react:
specifier: ^19.2.6 specifier: ^19.2.6
version: 19.2.7 version: 19.2.7
@ -312,6 +315,15 @@ packages:
'@rolldown/pluginutils@1.0.1': '@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
'@tanstack/react-virtual@3.14.2':
resolution: {integrity: sha512-IpWnmCLvuymRfeeLNVXIzNEYBFLpd3drVIS91sqV78VTZFyldlChkOocZRCPp1B+Wnk09bcLNme8WaMU/9/9bQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/virtual-core@3.17.0':
resolution: {integrity: sha512-gOxY/hFkPh/XQYhnThBHzkbkX3Ed+z/iushyz+R+JAr213aXxUDgQoTgTdrDpBSRsjFM73P/KfUyWmaF9WHMkQ==}
'@tybys/wasm-util@0.10.2': '@tybys/wasm-util@0.10.2':
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
@ -1172,6 +1184,14 @@ snapshots:
'@rolldown/pluginutils@1.0.1': {} '@rolldown/pluginutils@1.0.1': {}
'@tanstack/react-virtual@3.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
'@tanstack/virtual-core': 3.17.0
react: 19.2.7
react-dom: 19.2.7(react@19.2.7)
'@tanstack/virtual-core@3.17.0': {}
'@tybys/wasm-util@0.10.2': '@tybys/wasm-util@0.10.2':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1

View file

@ -1,122 +1,40 @@
import { useState } from 'react' import { useRef } from 'react';
import reactLogo from './assets/react.svg' import { useVirtualizer } from '@tanstack/react-virtual';
import viteLogo from './assets/vite.svg' import { useDashboardData } from './hooks/useDashboardData';
import heroImg from './assets/hero.png' import './App.css';
import './App.css'
function App() { export default function App() {
const [count, setCount] = useState(0) const data = useDashboardData();
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: data.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 35, // Consistent row height
});
return ( return (
<> <div className="dashboard-container">
<section id="center"> <h1>Market Live Stream</h1>
<div className="hero">
<img src={heroImg} className="base" width="170" height="179" alt="" /> {/* Viewport for virtualization */}
<img src={reactLogo} className="framework" alt="React logo" /> <div ref={parentRef} className="table-viewport">
<img src={viteLogo} className="vite" alt="Vite logo" /> <div
</div> className="table-content"
<div> style={{ height: `${rowVirtualizer.getTotalSize()}px` }}
<h1>Get started</h1>
<p>
Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
</p>
</div>
<button
type="button"
className="counter"
onClick={() => setCount((count) => count + 1)}
> >
Count is {count} {rowVirtualizer.getVirtualItems().map((virtualRow) => (
</button> <div
</section> key={virtualRow.key}
className="row"
<div className="ticks"></div> style={{ transform: `translateY(${virtualRow.start}px)` }}
>
<section id="next-steps"> <span>{data[virtualRow.index].symbol}</span>
<div id="docs"> <span>${data[virtualRow.index].price}</span>
<svg className="icon" role="presentation" aria-hidden="true"> </div>
<use href="/icons.svg#documentation-icon"></use> ))}
</svg>
<h2>Documentation</h2>
<p>Your questions, answered</p>
<ul>
<li>
<a href="https://vite.dev/" target="_blank">
<img className="logo" src={viteLogo} alt="" />
Explore Vite
</a>
</li>
<li>
<a href="https://react.dev/" target="_blank">
<img className="button-icon" src={reactLogo} alt="" />
Learn more
</a>
</li>
</ul>
</div> </div>
<div id="social"> </div>
<svg className="icon" role="presentation" aria-hidden="true"> </div>
<use href="/icons.svg#social-icon"></use> );
</svg> }
<h2>Connect with us</h2>
<p>Join the Vite community</p>
<ul>
<li>
<a href="https://github.com/vitejs/vite" target="_blank">
<svg
className="button-icon"
role="presentation"
aria-hidden="true"
>
<use href="/icons.svg#github-icon"></use>
</svg>
GitHub
</a>
</li>
<li>
<a href="https://chat.vite.dev/" target="_blank">
<svg
className="button-icon"
role="presentation"
aria-hidden="true"
>
<use href="/icons.svg#discord-icon"></use>
</svg>
Discord
</a>
</li>
<li>
<a href="https://x.com/vite_js" target="_blank">
<svg
className="button-icon"
role="presentation"
aria-hidden="true"
>
<use href="/icons.svg#x-icon"></use>
</svg>
X.com
</a>
</li>
<li>
<a href="https://bsky.app/profile/vite.dev" target="_blank">
<svg
className="button-icon"
role="presentation"
aria-hidden="true"
>
<use href="/icons.svg#bluesky-icon"></use>
</svg>
Bluesky
</a>
</li>
</ul>
</div>
</section>
<div className="ticks"></div>
<section id="spacer"></section>
</>
)
}
export default App

View file

@ -0,0 +1,23 @@
import { useEffect, useState } from 'react';
export const useDashboardData = () => {
const [data, setData] = useState<any[]>([]);
useEffect(() => {
// Instantiate the worker
const worker = new Worker(new URL('../worker/data.worker.ts', import.meta.url), {
type: 'module',
});
worker.onmessage = (event) => {
if (event.data.type === 'BATCH_UPDATE') {
// Keep only the latest 1000 items to prevent memory bloat
setData((prev) => [...event.data.payload, ...prev].slice(0, 1000));
}
};
return () => worker.terminate();
}, []);
return data;
};

13
src/worker/data.worker.ts Normal file
View file

@ -0,0 +1,13 @@
// src/worker/data.worker.ts
const generateUpdate = () => ({
id: Math.random().toString(36).substring(7),
symbol: "BTC/USD",
price: (Math.random() * 50000).toFixed(2),
timestamp: Date.now(),
});
// Stream updates every 16ms (target ~60fps)
setInterval(() => {
const updates = Array.from({ length: 5 }, generateUpdate);
self.postMessage({ type: 'BATCH_UPDATE', payload: updates });
}, 16);

View file

@ -1,7 +1,11 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react';
// https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
}) worker: {
// This ensures your worker is bundled as an ES module,
// which is required for modern browsers and deployment.
format: 'es',
},
});