如何匹配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
相关产品推荐
相关产品推荐

