如何创建百万行CSV并通过React+Node展示?Node存库报错求助
问题解决与方案
1. 修复"Cannot access 'fs' before initialization"错误
错误根源是require(fs)里的模块名没加引号,必须用字符串'fs';另外你的自执行函数没加()调用,导致代码根本没执行。修正后的CSV生成代码:
const fs = require('fs'); (async () => { const writeStream = fs.createWriteStream('out.csv'); // 改成百万行(1e8是1亿,会生成几十GB文件,资源消耗极大) for(let i=0; i< 1e6; i++){ const over = writeStream.write(`${i},1\n`); if(!over){ await new Promise((resolve) => writeStream.once('drain', resolve)); } } writeStream.end(); })(); // 必须加()执行自执行函数
2. 修正CSV读取代码并实现数据库存储
你的读取代码存在多处问题:chunkString在data事件外部无法访问、自执行函数未调用、readStream在函数作用域外部无法获取。下面是修正后的代码,同时集成MySQL批量存储(需先安装mysql2:npm install mysql2):
完整读取+存储代码
const fs = require('fs'); const mysql = require('mysql2/promise'); // 创建数据库连接池 const pool = mysql.createPool({ host: 'localhost', user: '你的数据库用户名', password: '你的数据库密码', database: '你的数据库名', connectionLimit: 10, // 控制并发连接数 queueLimit: 0 }); // 初始化数据表(首次运行执行) async function initTable() { await pool.execute(` CREATE TABLE IF NOT EXISTS csv_data ( id INT PRIMARY KEY, cost FLOAT NOT NULL ) `); } (async () => { await initTable(); const readStream = fs.createReadStream('out.csv', 'utf8'); let unprocessed = ''; let batch = []; const BATCH_SIZE = 1000; // 批量插入,减少数据库请求次数 readStream.on('data', async (chunk) => { let chunkString = unprocessed + chunk.toString(); unprocessed = ''; let startIndex = 0; for(let ch = startIndex; ch < chunkString.length; ch++){ if(chunkString[ch] === '\n'){ const line = chunkString.slice(startIndex, ch); const idx = line.indexOf(','); if(idx === -1) { startIndex = ch + 1; continue; } const id = parseInt(line.slice(0, idx)); const cost = parseFloat(line.slice(idx + 1)); batch.push([id, cost]); // 达到批量阈值就插入数据库 if(batch.length >= BATCH_SIZE) { await pool.execute( 'INSERT INTO csv_data (id, cost) VALUES ?', [batch] ); batch = []; } startIndex = ch + 1; } } // 处理chunk末尾未完成的行 const remaining = chunkString.slice(startIndex); if(remaining) unprocessed = remaining; }); readStream.on('end', async () => { // 插入剩余的最后一批数据 if(batch.length > 0) { await pool.execute( 'INSERT INTO csv_data (id, cost) VALUES ?', [batch] ); } console.log('所有数据已存入数据库'); await pool.end(); }); readStream.on('error', (err) => { console.error('读取文件出错:', err); }); })();
3. 百万行CSV文件的高效创建
保持流写入的方式即可,流会逐行写入磁盘,不会一次性占用大量内存。注意不要轻易尝试1亿行,这会生成几十GB的文件,对存储和CPU压力极大,百万行是更合理的测试量级。
4. React + Node.js 展示百万行数据
直接渲染百万行会导致React卡顿甚至崩溃,必须用虚拟列表+后端分页的方案:
Node.js 后端(分页接口)
const express = require('express'); const mysql = require('mysql2/promise'); const cors = require('cors'); // 解决跨域 const app = express(); const port = 3001; app.use(cors()); const pool = mysql.createPool({ host: 'localhost', user: '你的数据库用户名', password: '你的数据库密码', database: '你的数据库名', }); // 分页查询接口 app.get('/api/csv-data', async (req, res) => { const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 100; const offset = (page - 1) * pageSize; try { const [rows] = await pool.execute( 'SELECT id, cost FROM csv_data LIMIT ? OFFSET ?', [pageSize, offset] ); const [countResult] = await pool.execute('SELECT COUNT(*) as total FROM csv_data'); res.json({ data: rows, total: countResult[0].total, page, pageSize }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.listen(port, () => { console.log(`后端服务运行在 http://localhost:${port}`); });
React 前端(虚拟列表实现)
先安装依赖:npm install react-window axios
然后编写展示组件:
import { useState, useEffect } from 'react'; import { FixedSizeList as List } from 'react-window'; import axios from 'axios'; function CsvDataViewer() { const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const pageSize = 100; useEffect(() => { fetchData(page); }, [page]); const fetchData = async (currentPage) => { try { const response = await axios.get(`http://localhost:3001/api/csv-data`, { params: { page: currentPage, pageSize } }); setData(response.data.data); setTotal(response.data.total); } catch (err) { console.error('获取数据失败:', err); } }; const Row = ({ index, style }) => { const item = data[index]; return ( <div style={{...style, display: 'flex', justifyContent: 'space-between', padding: '0 10px', borderBottom: '1px solid #eee'}}> <span>ID: {item.id}</span> <span>成本: {item.cost}</span> </div> ); }; return ( <div style={{maxWidth: '800px', margin: '0 auto'}}> <h1>CSV数据展示</h1> <div style={{ height: '600px', border: '1px solid #ccc', borderRadius: '4px' }}> <List height={600} itemCount={data.length} itemSize={36} width="100%" > {Row} </List> </div> <div style={{display: 'flex', justifyContent: 'center', gap: '16px', marginTop: '16px'}}> <button onClick={() => setPage(prev => Math.max(prev-1, 1))} disabled={page === 1}>上一页</button> <span>第 {page} 页 / 共 {Math.ceil(total/pageSize)} 页</span> <button onClick={() => setPage(prev => prev+1)} disabled={page >= Math.ceil(total/pageSize)}>下一页</button> </div> </div> ); } export default CsvDataViewer;
内容的提问来源于stack exchange,提问作者CoderInUi
相关产品推荐
相关产品推荐

