You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何匹配ID与WebSocket返回对象ID,仅更新对应列表项的价格

问题根源

代码核心问题是用了全局单一的price状态存储所有币种的价格,导致每次更新价格所有行都会被同步修改,无法对应到指定ID的币种。同时key属性放置位置错误、WebSocket消息回调重复绑定也会带来异常和性能问题。


修复方案

1. 改造ProductRow组件

用React.memo包裹组件实现浅比较,仅自身id或price变化时才触发重渲染,避免无关更新。同时将key属性移到列表渲染的根节点上,不要放在组件内部元素上:

import React from 'react';

export default React.memo(function ProductRow(props) {
    return <li><p>{ props.id }</p><p>{props.price}</p></li>;
});

2. 改造WatchList组件

  • 初始化币种数据时给每个币种新增初始price字段
  • 删除全局单一price状态,收到WebSocket推送时仅更新对应ID的币种价格
  • 修正WebSocket消息回调绑定逻辑,避免重复绑定,新增组件卸载时主动关闭连接的逻辑:
import React, { useState, useEffect, useRef } from "react";
import { Button } from 'react-bootstrap';
import ProductRow from "./ProductRow";

export default function WatchList() {
  const [currencies, setCurrencies] = useState([]);
  const product_ids = currencies.map((cur) => cur.id);
  const [isToggle, setToggle] = useState(false);
  const ws = useRef(null);

  const first = useRef(false);
  const url = "https://api.pro.coinbase.com";

  useEffect(() => {
    ws.current = new WebSocket("wss://ws-feed.pro.coinbase.com");

    const apiCall = async () => {
      const res = await fetch(url + "/products");
      const pairs = await res.json();
      
      let filtered = pairs.filter((pair) => pair.quote_currency === "USD");
      filtered = filtered.sort((a, b) => a.base_currency.localeCompare(b.base_currency));
      // 给每个币种增加初始price字段
      const initCurrencies = filtered.map(item => ({...item, price: '0.00'}));
      
      setCurrencies(initCurrencies);
      first.current = true;
    };
    apiCall();

    // 组件卸载时关闭WebSocket连接
    return () => {
      ws.current.close();
    }
  }, []);
  

  useEffect(() => {
    ws.current.onmessage = (e) => {
      if (!first.current) return;
      const data = JSON.parse(e.data);
      if (data.type !== "ticker") return;
      
      // 只更新对应product_id的币种价格
      setCurrencies(prev => prev.map(item => {
        if (item.id === data.product_id) {
          return {...item, price: data.price};
        }
        return item;
      }));
    };
  }, []); // 依赖为空,仅绑定一次回调
  
  const handleToggleClick = () => {
    if (!isToggle) {
      const msg = {
        type: "subscribe",
        product_ids: product_ids,
        channels: ["ticker"]
      };
      ws.current.send(JSON.stringify(msg));
      setToggle(true);
      console.log('Toggled On');
    } else {
      const unsubMsg = {
        type: "unsubscribe",
        product_ids: product_ids,
        channels: ["ticker"]
      };
      ws.current.send(JSON.stringify(unsubMsg));
      setToggle(false);
      console.log('Toggled Off');
    }
  };

  return (
    <div className="container">
      <Button onClick={handleToggleClick}><p className="mb-0">Toggle</p></Button>
      <ul>
        {/* key放在map渲染的组件根节点上 */}
        {currencies.map((cur) => (
          <ProductRow key={cur.id} id={cur.id} price={cur.price} />
        ))}
      </ul>
    </div>
  );
}

3. App.js无逻辑错误,保持原有写法即可。

修改后即可实现收到ticker推送时,仅对应ID的币种行更新价格,其他行不会触发不必要的重渲染。


内容的提问来源于stack exchange,提问作者reactnoob1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 08:45:01