nodejs43 (UI相关) : TradingView专业金融图表库 + react useEffect 动态加载与使用TradingView.widget的demo

TradingView

  • TradingView是专业金融图表与社交交易平台,集图表分析、全球市场数据、策略研究和社交交流于一体 ,支持股票、外汇、加密货币、期货等多种资产。

在这里插入图片描述

  • 它提供专业级的技术指标和绘图工具,还能用 Pine Script 自定义策略,并可连接部分券商账户直接交易;同时,用户还能在社区分享和浏览交易想法,像投资者的“社交网络”一样交流经验。

在这里插入图片描述

  • 平台支持网页、桌面和手机多端使用,新手能轻松上手,资深交易者也能深度挖掘。

在这里插入图片描述

在这里插入图片描述

  • TradingView.widget (https://www.tradingview.com/widget/)是 TradingView 官方提供的小部件,它自带了 TradingView 的行情数据源不需要用户自己接入交易所或外汇的 API
  • TradingView 会自动拉取并展示它支持的市场(外汇、股票、加密货币、期货等)的实时/延迟数据。

    • 股票类 → 一般免费用户看到的是 延时数据(通常 15 分钟延迟)。
    • 外汇/加密货币 → 大部分是 实时数据,因为这些数据源 TradingView 可以免费分发。
    • 如果需要 全市场实时数据(比如 NASDAQ/NYSE 实时行情),TradingView 通常会要求开通 付费订阅 或交易所授权。
  • 常见的 TradingView symbol
类别示例 Symbol含义
外汇 ForexFX:EURUSD欧元 / 美元
FX:USDJPY美元 / 日元
FX:GBPUSD英镑 / 美元
FX:AUDUSD澳元 / 美元
FX:USDCAD美元 / 加元
美股 US StocksNASDAQ:AAPL苹果公司
NASDAQ:MSFT微软
NASDAQ:TSLA特斯拉
NYSE:IBMIBM
NYSE:JPM摩根大通
加密货币 CryptoBINANCE:BTCUSDT比特币/USDT(币安)
BINANCE:ETHUSDT以太坊/USDT(币安)
COINBASE:BTCUSD比特币/美元(Coinbase)
KRAKEN:ETHUSD以太坊/美元(Kraken)
指数 IndexSP:SPX标普500指数
DJ:DJI道琼斯工业指数
NASDAQ:NDX纳斯达克100
INDEX:NIKKEI日经225
INDEX:HSI恒生指数
大宗商品 CommoditiesTVC:GOLD黄金现货
TVC:SILVER白银现货
TVC:USOILWTI 原油
TVC:UKOIL布伦特原油
  • 官方提供了使用代码示例:

在这里插入图片描述

加载关键代码

  • 因为 TradingView 的脚本不是 React/Next.js 内置的模块,而是一个外部的全局库。我们得按照它的特性来“引入 → 等待加载 → 使用”,否则会报错或者无法正常渲染。
  1. Next.js 的 SSR(服务端渲染)环境没有 window

    • Next.js 默认会在服务端也尝试运行组件。
    • 服务端没有 DOM 和 window,所以 window.TradingView 根本不可能存在。
    • 如果不加 "use client" 或不在 useEffect 里调用,会直接在服务端渲染时报错。
  2. React 生命周期的节奏

    • React 在组件挂载前执行 JSX → DOM 元素还没生成。
    • 你需要等到组件“挂载”之后,容器 <div id="tradingview_widget"> 已经出现在 DOM 里,再去告诉 TradingView 把图表插进去。
    • useEffect 正好就是“等 DOM 渲染完再执行副作用”的钩子。
  • 负责 动态加载 TradingView 提供的外部脚本(tv.js) 的代码:
useEffect(() => {
  // Load TradingView widget script
  const script = document.createElement("script") // 创建一个新的 <script>元素
  script.src = "https://s3.tradingview.com/tv.js" // 设置 script 的加载地址,这个库会往window挂载一个全局window.TradingView对象
  script.async = true                             // 告诉浏览器异步加载脚本,不会阻塞页面渲染
  script.onload = () => setIsLoaded(true)         // 当脚本加载完成时触发 onload 回调
  document.body.appendChild(script)              // 把 <script> 元素插入到 <body> 里,浏览器开始去下载并执行它

  return () => { // 清理函数,会在组件卸载时执行。作用是把之前插入的`<script> 从 DOM 中移除,避免内存泄漏或重复加载。
    document.body.removeChild(script)
  }
}, []) // 依赖数组 `[]` 是空的 → 代表这个 effect **只在挂载时运行一次**,卸载时运行 cleanup。
  • 在组件挂载时动态引入 TradingView 的外部库,并在加载完成后更新状态;当组件卸载时,再把这个 script 元素移除。

使用代码 这个脚本是如何被使用的?

  • 脚本加载后,window.TradingView 出现。
  • 里面有一个构造函数 TradingView.widget(...),这是 TradingView 提供的 API,用来在页面上生成交互式行情图表。
  • 在脚本 onload 事件触发时,组件里执行了 setIsLoaded(true)
  • 这就会触发 第二个 useEffect
    • new window.TradingView.widget({...}) 会在页面上渲染一个 TradingView 图表。
    • 其中的 container_id: "tradingview_widget" 指定了图表要挂载到哪个 DOM 节点。这个 id 对应的 JSX 里写的 <div id="tradingview_widget">...</div>
useEffect(() => {
  if (isLoaded && containerRef.current) { // 当脚本已经加载完成 (isLoaded 为 true) 且容器 div 已经存在时,才初始化 TradingView 的图表小部件
    new window.TradingView.widget({
      autosize: true,                    // 让图表根据容器大小自动伸缩
      symbol: "FX:EURUSD",               // 设置初始显示的交易品种,这里是外汇 EUR/USD
      interval: "D",                     // 图表的时间间隔,这里是 "D" → 日线
      timezone: "Etc/UTC",              // 设置时区,这里用 UTC
      theme: "dark",                    // 图表主题,"dark" → 黑色背景
      style: "1",                       // 图表样式编号,"1" 表示蜡烛图(TradingView 内置的样式类型)
      locale: "en",                     // 图表语言,这里是英文
      toolbar_bg: "#131722",            // 工具栏背景颜色
      enable_publishing: false,           // 是否允许用户发布分享自己的图表,false 表示关闭
      hide_top_toolbar: false,         // 是否隐藏顶部工具栏,false 表示显示
      hide_legend: false,               // 是否隐藏图例,false 表示显示
      save_image: false,               // 是否允许保存图表为图片,false 表示不允许
      container_id: "tradingview_widget", // 指定挂载图表的容器元素 id
    })
  }
}, [isLoaded]) // 依赖项是 isLoaded → 只有当脚本加载状态改变时才会运行
  • TradingView 的代码会往这个 <div> 里插入一个 iframe,里面就是 TradingView 提供的实时行情图表。
  • 最终用户就能在页面上看到一个可以缩放、切换时间周期、加指标的互动图表。

多市场切换的 TradingView Demo

demo运行与效果

# 如果你还没有项目,可以先新建一个:
npx create-next-app@latest tradingview-demo
cd tradingview-demo
app/tradingview/page.tsx # 用于放置TradingViewSwitcher代码
npm run dev

在这里插入图片描述

代码

"use client"

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

declare global {
  interface Window {
    TradingView: any
  }
}

export default function TradingViewPage() {
  const containerRef = useRef<HTMLDivElement>(null)
  const [isLoaded, setIsLoaded] = useState(false)
  const [symbol, setSymbol] = useState("FX:EURUSD") // 当前选择的交易对

  // 加载 TradingView 脚本
  useEffect(() => {
    const script = document.createElement("script")
    script.src = "https://s3.tradingview.com/tv.js"
    script.async = true
    script.onload = () => setIsLoaded(true)
    document.body.appendChild(script)

    return () => {
      document.body.removeChild(script)
    }
  }, [])

  // 初始化 / 更新 TradingView 图表
  useEffect(() => {
    if (isLoaded && containerRef.current) {
      containerRef.current.innerHTML = "" // 清空旧图表,避免重叠

      new window.TradingView.widget({
        autosize: true,
        symbol: symbol,
        interval: "D",
        timezone: "Etc/UTC",
        theme: "dark",
        style: "1",
        locale: "en",
        container_id: "tradingview_container",
      })
    }
  }, [isLoaded, symbol]) // 每次 symbol 改变都会刷新图表

  return (
    <section className="py-16 px-4 md:px-8">
        <div className="max-w-6xl mx-auto text-left mb-10">
        <h2 className="text-3xl md:text-4xl font-bold mb-4">
            Live Market Analysis
        </h2>
        <p className="text-gray-400 max-w-2xl">
            Track real-time market movements with professional-grade charts.
        </p>
        </div>

        <div className="bg-gray-900 border border-gray-700 rounded-2xl shadow-md overflow-hidden">
            {/* Tabs 按钮 */}
            <div className="flex border-b border-gray-700">
            <button
                className={`flex-1 py-2 text-sm font-medium ${
                symbol === "FX:EURUSD" ? "text-white bg-gray-800" : "text-gray-400 hover:text-white"
                }`}
                onClick={() => setSymbol("FX:EURUSD")}
            >
                EUR/USD
            </button>
            <button
                className={`flex-1 py-2 text-sm font-medium ${
                symbol === "FX:GBPUSD" ? "text-white bg-gray-800" : "text-gray-400 hover:text-white"
                }`}
                onClick={() => setSymbol("FX:GBPUSD")}
            >
                GBP/USD
            </button>
            <button
                className={`flex-1 py-2 text-sm font-medium ${
                symbol === "FX:USDJPY" ? "text-white bg-gray-800" : "text-gray-400 hover:text-white"
                }`}
                onClick={() => setSymbol("FX:USDJPY")}
            >
                USD/JPY
            </button>
            <button
                className={`flex-1 py-2 text-sm font-medium ${
                symbol === "OANDA:XAUUSD" ? "text-white bg-gray-800" : "text-gray-400 hover:text-white"
                }`}
                onClick={() => setSymbol("OANDA:XAUUSD")}
            >
                XAU/USD
            </button>
            </div>

            {/* TradingView 容器 */}
            <div
            ref={containerRef}
            id="tradingview_container"
            className="w-full h-[600px]"
            ></div>
        </div>
    </section>
  )
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值