Skip to content

给表格添加其它实时响应数据,并保持性能最佳 ​

本节目标:理解“给表格添加其它实时响应数据,并保持性能最佳”的核心思路,并能把它用于实际开发或面试表达。 在虚拟化表格里再接入 “更多实时字段” 的关键思路一句话:把数据流和渲染彻底解耦,用 集中队列 + requestAnimationFrame 批量冲刷 + 虚拟滚动只订阅可视行,其余都走缓存或惰性计算。

1 . 数据来源层 —— 统一 WebSocket 消息流

做法目的
所有实时字段(价差、成交量、仓位比、深度…)共用一条 WS 或 mux 多条 WS减少握手/心跳开销
解析后不要直接改行数据,先写入 Map<symbol, Partial<UpdatePayload>>把“变”收集起来,防抖
不同字段分队列 or 同队列但字段分片:priceQueue / volumeQueue …方便不同刷新节奏

interface UpdatePayload { bid?: number ask?: number spread?: number vol?: number // …按需扩展} 2 . 批量刷新层 —— requestAnimationFrame 冲刷 const queues = new Map<string, UpdatePayload[]>() function scheduleFlush() { if (rafId === null) rafId = requestAnimationFrame(flush)} function flush() { queues.forEach((list, sym) => { const snapshot = Object.assign({}, ...list) // 把一帧内同 symbol 的更新合并 list.length = 0 applyToRow(sym, snapshot) // 只对 Map/Ref 做一次写入 applyToOpenOrder(sym, snapshot) // 如需同步到其他表格 }) rafId = null} *️⃣ 一个 raf 批一次,浏览器一次重排、一次重绘,表格行只更新真正变动的字段。

3 . 状态存储层 —— Pinia Store “真源” + Weak 引用

  • const rowMap = computed(()=> …) 已经很好;继续保持 Symbol → 行对象。
  • 新增字段时在 applyToRow 里 按需动态添加,Vue 3 的 Proxy 能自动追踪。
  • 不要在行对象塞大对象,如深度列表等可存在 Map 里,只给行对象挂一个 hasDepth=true 标识。

4 . 视图层 —— 虚拟滚动 + 只订阅可视行

  1. onVisibleChange 拿到当前可视 symbols
  2. tradeViewStore.subscribe([...visible], 'update')——已有
  3. 丢给 feedStore / depthStore / whateverStore 拉其余字段
  4. 滚出视口的行 → unsubscribe(除非其它组件还在用)实践:一个 3,000 行列表,只订阅 30 – 50 行,轻松保持 < 1 kB/s

5 . 性能兜底

场景手段
爆量推送(几万条/秒)WebSocket Server 端做 diff/聚合;前端可用 WebWorker 解析
深度更新巨大单独的 <DepthPanel> 弹窗时才订阅;表格里只给“有深度”提示
频繁 setState 还是卡markRaw or shallowRef 行对象,避免 Vue 深层递归 diff

简要流程图 WebSocket → Queue<Map> → rAF flush ─┬─> rowMap / reactive → 虚拟表格 └─> 其它组件(指标面板…) 核心总结

  1. 集中队列 而非逐条 set → 降低更新频率
  2. rAF 对齐浏览器节奏 → 减少重排
  3. 虚拟滚动 + 可视订阅 → 减少网络和内存
  4. Store 单一真源 → 易维护、低耦合

这样再加任何实时字段都只是 “多一个队列 + 一段合并逻辑”,性能稳住,代码也清晰。

这种做法是不是最佳实践,再给我重复一下完整的实现,一步步提供

ChatGPT 说:先回答“是不是最佳实践?”

  • 解耦 FeedStore 专管 外部请求 + 本地缓存,tradeViewStore 只管 展示层数据,这是 Store 单一职责 的典型做法;
  • 单一真源 所有 Feed 都统一落到 feedMap,行数据只是引用,避免同一份数据分散多处;
  • 响应式注入 用 watch 把 Feed 注回表格,没有手动 setState,符合 Vue 响应式 思路;
  • 按需拉取 只对“可见行”调用 fetchFeeds,避免大量无用请求。总体是当前 Pinia / Vue3 项目里较通行的写法。如果后期发现性能瓶颈,可再做 批量接口/LRU 缓存 等优化。

完整实现(一步步) 下面把所有改动拆成 4 步,你照顺序粘贴即可。

步骤 1 — 类型与常量 types/feed.ts

ts 复制编辑 // Feed 服务器返回的 article → 转成的业务对象export interface FeedInfo { header: { code: Array<{ _type: string; __text: string }> option: { watch: Array<{ _type: string; __text: string }> } term: string } story: { title: string // …有需要再补字段 } value?: string // bullish/bearish 数字,提前算好} /** 解析 XML 用到的临时接口(外部库 xml2js 的输出) /export interface XmlJson { items?: { article: FeedAnalysis | FeedAnalysis[] }} export interface FeedAnalysis { analysis: { content: { header: FeedInfo['header'] story: FeedInfo['story'] } }} /* term → watch._type 的映射 */export const TERM_OPINION_MAP: Record<string, string> = { short: 'OPINION_SHORT', medium: 'OPINION_MEDIUM', long: 'OPINION_LONG',} 在你已有 TableRow 上加两个字段(tradeViewStore.ts 里或公共类型文件):

ts 复制编辑 export interface TableRow extends ForexSymbol { ... // 之前的 feed?: FeedInfo feedValue?: number}

步骤 2 — FeedStore stores/useFeedStore.ts

ts 复制编辑 import { defineStore } from 'pinia'import { ref } from 'vue'import { http } from '@/utils'import xmlparser from 'fast-xml-parser' // 你项目里已有import type { FeedInfo, XmlJson, FeedAnalysis } from '@/types/feed'import { TERM_OPINION_MAP } from '@/types/feed' export default defineStore('feed', () => { /** symbol → FeedInfo / const feedMap = ref<Map<string, FeedInfo>>(new Map()) /* 外部调用,按 symbol 数组批量拉取 */ async function fetchFeeds(symbols: string[]) { if (!symbols.length) return const symbolStr = symbols.join(',') try { const resp = await http.get('/api/pub/get_feednews', { params: { type_product: 'null', product: symbolStr, term: 'null', culture: 'en', days: 1, last_ta: 1, }, transformResponse: (d: any) => d, }) const xmlStr = resp.data as string if (typeof xmlStr !== 'string' || !xmlStr) return const xmlJson: XmlJson = xmlparser.parse(xmlStr) const rawArticles = xmlJson.items?.article if (!rawArticles) return const articles = Array.isArray(rawArticles) ? rawArticles : [rawArticles] articles.forEach((art: FeedAnalysis) => { const header = art.analysis?.content?.header const story = art.analysis?.content?.story if (!header || !story) return const ticker = header.code.find((c) => c._type === 'TICKER')?.__text if (!ticker) return const opinionKey = TERM_OPINION_MAP[header.term] const opinion = header.option.watch.find((w) => w._type === opinionKey)?.__text ?? '' const feed: FeedInfo = { header, story, value: opinion } feedMap.value.set(ticker, feed) }) } catch (e) { // 失败不抛给调用方,统一记录 console.error('fetchFeeds error', e) } } return { feedMap, fetchFeeds }})

步骤 3 — tradeViewStore 集成 stores/useTradeViewStore.ts 增量改动

ts 复制编辑 // 顶部 importimport useFeedStore from '@/stores/useFeedStore'...const feedStore = useFeedStore() 3-a. 更新整表时注入 Feed

ts 复制编辑 const updateFullSymbolList = (newList: TableRow[]) => { const m = feedStore.feedMap newList.forEach((row) => { const feed = m.get(row.symbol) if (feed) { row.feed = feed row.feedValue = +feed.value! || 0 } }) fullSymbolList.value = newList} 3-b. 监听 FeedMap 实时回填

ts 复制编辑 import { watch } from 'vue'// …watch( () => feedStore.feedMap, (m) => { fullSymbolList.value.forEach((row) => { const f = m.get(row.symbol) if (f) { row.feed = f row.feedValue = +f.value! || 0 } }) }, { deep: true }) 3-c. 可见行更新时顺带拉取 Feed 原来的 handler:

ts 复制编辑 function handleVisibleDataUpdated(symbols: string[]): void { tradeViewStore.subscribe(symbols, 'update')} 改成:

ts 复制编辑 function handleVisibleDataUpdated(symbols: string[]): void { tradeViewStore.subscribe(symbols, 'update') feedStore.fetchFeeds(symbols).catch(console.error)}

步骤 4 — 组件渲染不变 因为 TableRow 已经多了 feed / feedValue,你原来的列 render 逻辑能直接工作,无需再改。

✅ 至此功能完整

  1. SymbolList 滚动 → 触发 visible-data-updated
  2. tradeViewStore.subscribe 保证价格 WebSocket;同一步骤调用 feedStore.fetchFeeds
  3. 拉回的 Feed 更新 feedMap → 被 watch 回写行对象
  4. 表格 cell 根据 record.feed 渲染箭头、tooltip
  5. 点击行弹窗可直接读取 record.feed.story 详细内容

如果还有性能或复用需求,再考虑:

  • 持久化 feedMap(Pinia Persist 或 IndexedDB);
  • 接口去抖 / 批量缓存;
  • 把 feedValue 做成计算属性 而非冗余字段等。

这样就把“TA/feed 分析”跟实时报价 & 行情表格彻底解耦,同时保持响应式一致性,符合 Vue3 + Pinia 的最佳实践范式。

本站总访问 — 次