React中数据发生变化时如何触发运行临时特效?
React实现DOM更新后触发临时特效的方案
方案1:状态驱动实现(优先推荐)
React本身是状态驱动UI更新的,我们不需要额外监听DOM变化,直接对比新旧接口数据就能精准定位到需要加特效的元素,是最符合React设计理念的实现方式:
实现步骤
- 接口请求返回新数据时,和旧数据做diff,记录所有价格发生变化的股票id
- 用状态变量存储这部分变动的id集合
- 渲染时给对应id的价格span添加特效类
- 开启定时器,2秒后清空变动id集合自动移除特效类
代码示例
import { useState, useEffect } from 'react' // 特效对应的CSS,可自行调整动画效果 const priceFlashCss = ` .price-flash { animation: flash 0.5s ease-in-out 2; } @keyframes flash { 0% { background-color: transparent; } 50% { background-color: #ffeb3b; } 100% { background-color: transparent; } } ` function StockPriceList() { const [stockList, setStockList] = useState([]) const [changedIds, setChangedIds] = useState(new Set()) // 模拟请求接口 const fetchStockData = async () => { const res = await fetch('/your/api/url') const newStockList = await res.json() // 对比新旧数据,找出价格变动的id const newChangedIds = new Set() newStockList.forEach(newItem => { const oldItem = stockList.find(item => item.id === newItem.id) if (oldItem && oldItem.price !== newItem.price) { newChangedIds.add(newItem.id) } }) setStockList(newStockList) setChangedIds(newChangedIds) // 2秒后移除特效 setTimeout(() => { setChangedIds(new Set()) }, 2000) } // 组件挂载后首次请求,之后每隔一段时间刷新 useEffect(() => { fetchStockData() const timer = setInterval(fetchStockData, 5000) return () => clearInterval(timer) }, [stockList]) return ( <div> <style>{priceFlashCss}</style> {stockList.map(item => ( <div key={item.id}> {item.ticker} Price : <span className={changedIds.has(item.id) ? 'price-flash' : ''}>{item.price}</span> </div> ))} </div> ) }
方案2:MutationObserver实现(监听DOM变化的方案)
如果你确实需要脱离业务数据、直接监听DOM的变更(比如渲染逻辑封装在第三方组件内无法拿到原始数据对比的场景),可以直接使用原生MutationObserver实现:
代码示例
import { useRef, useEffect, useState } from 'react' const priceFlashCss = ` .price-flash { animation: flash 0.5s ease-in-out 2; } @keyframes flash { 0% { background-color: transparent; } 50% { background-color: #ffeb3b; } 100% { background-color: transparent; } } ` function StockPriceList() { const [stockList, setStockList] = useState([]) const listRef = useRef(null) // 模拟请求接口的逻辑和之前一致 const fetchStockData = async () => { const res = await fetch('/your/api/url') const newStockList = await res.json() setStockList(newStockList) } useEffect(() => { fetchStockData() const timer = setInterval(fetchStockData, 5000) return () => clearInterval(timer) }, []) // 初始化MutationObserver监听DOM变化 useEffect(() => { if (!listRef.current) return const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { // 只监听价格span的文本变化 if (mutation.type === 'characterData' && mutation.target.parentElement.tagName === 'SPAN') { const spanEl = mutation.target.parentElement spanEl.classList.add('price-flash') setTimeout(() => { spanEl.classList.remove('price-flash') }, 2000) } }) }) observer.observe(listRef.current, { subtree: true, // 监听所有后代元素 characterData: true, // 监听文本内容变化 childList: true }) // 组件卸载时断开监听避免内存泄漏 return () => observer.disconnect() }, []) return ( <div ref={listRef}> <style>{priceFlashCss}</style> {stockList.map(item => ( <div key={item.id}> {item.ticker} Price : <span>{item.price}</span> </div> ))} </div> ) }
注意事项
该方案需要额外维护DOM监听逻辑,性能略低于状态驱动的方案,仅推荐在无法拿到原始数据做对比的场景使用。
内容的提问来源于stack exchange,提问作者reaver lover
相关产品推荐
相关产品推荐

