Enterprise UI
指南组件面试复习GitHub
开始
介绍快速开始主题定制Changeset 版本管理Turborepo + Changesets 发包方案
基础组件
Button 按钮Input 输入框Calendar 日历VirtualList 虚拟列表FileUploader 大文件上传Theme 全局换肤
SSE 实时行情与汇率推送(亮点难点与百问百答)useSSEQuotes Hook 说明SSE 重构汇率轮询 APIWebWorker 交易场景面试深挖指南百万级并发 WebSocket 前端系统WebSocket 面试全攻略
FCP 的优化LCP 的优化INP 的优化FPS 的优化Service Worker 面试题Service WorkerCPU 密集型与 IO 密集型任务preload / prefetch / async / defer
CI/CD 优化
I18N 技术总结Next.js SSR 国际化优化 P7Next.js SSR 国际化优化 P7 详细版国际化 I18N 面试版本本地启动拉最新 json清理 LRU 缓存方法SSR 国际化 - 翻译缺失检查脚本

Released under the MIT License.

Copyright © 2026 Enterprise UI

目录
  • useSSEQuotes Hook 说明
  • 特性(P7+ 亮点)
  • API
  • 使用示例
  • 源码
  • 面试要点

useSSEQuotes Hook 说明

SSE 实时行情 Hook,用于在交易页订阅实时价格推送,解耦首屏与实时流:首屏用 React Query 静态数据,实时行情通过本 Hook 单独订阅。

特性(P7+ 亮点)

  • 断线指数退避重连:重连间隔从 1s 起,按 2 倍递增,上限 30s,避免雪崩
  • 连接状态:idle | connecting | connected | reconnecting | disconnected,便于 UI 展示连接状态
  • 按需启用:enabled 为 false 或未在浏览器环境时不建立连接
  • 卸载清理:组件卸载时关闭 EventSource、清除定时器,无泄漏

API

function useSSEQuotes(symbol?: string, enabled?: boolean): {
  status: SSEConnectionStatus
  lastQuote: QuoteMessage | null
  error: string | null
}
参数类型默认值说明
symbolstring'BTC/USDT'交易对(当前实现中服务端推送固定为 BTC/USDT,可扩展)
enabledbooleantrue是否建立 SSE 连接
返回值类型说明
status'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected'连接状态
lastQuote{ type, symbol, price, ts } | null最新一条行情
errorstring | null错误信息(如重连提示)

使用示例

import { useSSEQuotes } from '@/hooks/useSSEQuotes'

function TradingPanel() {
  const { status, lastQuote, error } = useSSEQuotes('BTC/USDT', true)

  return (
    <div>
      <span className={status === 'connected' ? 'text-green-500' : 'text-amber-500'}>
        {status === 'connected' && lastQuote
          ? `Live: ${lastQuote.symbol} ${lastQuote.price}`
          : status === 'reconnecting'
            ? 'Reconnecting…'
            : 'SSE /api/sse/quotes'}
      </span>
      {error && <p className="text-red-400">{error}</p>}
    </div>
  )
}

源码

'use client'

import { useEffect, useRef, useState } from 'react'

export type SSEConnectionStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected'

export interface QuoteMessage {
  type: string
  symbol: string
  price: string
  ts: number
}

const MIN_BACKOFF = 1000
const MAX_BACKOFF = 30_000
const BACKOFF_MULTIPLIER = 2

/**
 * SSE 实时行情 Hook(P7+ 亮点:断线指数退避重连 + 连接状态)
 * 解耦首屏与实时流,重连期间可继续用 React Query 缓存展示
 */
export function useSSEQuotes(symbol: string = 'BTC/USDT', enabled = true) {
  const [status, setStatus] = useState<SSEConnectionStatus>('idle')
  const [lastQuote, setLastQuote] = useState<QuoteMessage | null>(null)
  const [error, setError] = useState<string | null>(null)
  const backoffRef = useRef(MIN_BACKOFF)
  const eventSourceRef = useRef<EventSource | null>(null)
  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)

  useEffect(() => {
    if (!enabled || typeof window === 'undefined') return

    let mounted = true

    function connect() {
      if (!mounted) return
      const url = `${window.location.origin}/api/sse/quotes`
      setStatus('connecting')
      setError(null)
      const es = new EventSource(url)
      eventSourceRef.current = es

      es.onopen = () => {
        if (!mounted) return
        setStatus('connected')
        backoffRef.current = MIN_BACKOFF
      }

      es.onmessage = (e) => {
        if (!mounted) return
        try {
          const data = JSON.parse(e.data) as QuoteMessage
          setLastQuote(data)
        } catch {
          // ignore parse error
        }
      }

      es.onerror = () => {
        if (!mounted) return
        es.close()
        eventSourceRef.current = null
        setStatus('reconnecting')
        setError('Connection lost, reconnecting…')
        const delay = Math.min(backoffRef.current, MAX_BACKOFF)
        timeoutRef.current = setTimeout(() => {
          backoffRef.current *= BACKOFF_MULTIPLIER
          connect()
        }, delay)
      }
    }

    connect()

    return () => {
      mounted = false
      if (timeoutRef.current) clearTimeout(timeoutRef.current)
      eventSourceRef.current?.close()
      eventSourceRef.current = null
      setStatus('disconnected')
    }
  }, [enabled, symbol])

  return { status, lastQuote, error }
}

面试要点

  • 为什么用 SSE 而不是 WebSocket:服务端单向推送行情即可,SSE 基于 HTTP、自动重连、易配合现有网关与 CDN。
  • 重连策略:指数退避避免断网恢复瞬间大量重连打满服务端。
  • 与首屏解耦:首屏不依赖 SSE,用 BFF/React Query 数据渲染;SSE 仅做增量更新,保证首屏快、实时性不丢。