React中Matter.js组件Props更新后性能衰减问题求助
问题分析与解决方案
核心问题
- 资源清理不彻底:每次
coins更新都新建Matter引擎、渲染器,但afterUpdate事件监听未移除,且Runner实例未正确停止(原代码用Runner.stop(engine)是错误的,Runner是独立实例),导致旧的事件循环和资源残留。 - 图片资源重复创建:
afterUpdate回调里每次都new Image(),没有缓存,大量图片对象堆积占用内存。 - 冗余渲染器:你自己用Canvas绘制气泡,却同时启用了Matter自带的
Render,双重渲染额外消耗性能。 - 未复用核心实例:每次props更新都重建整个物理环境,完全没必要,应该复用引擎、世界、Canvas,仅更新物理体。
解决方案:复用核心资源,仅更新物理体
用useRef持久化引擎、世界、Runner、Canvas等实例,组件挂载时仅初始化一次;coins变化时,只替换物理体,同时缓存图片避免重复创建。
修改后的完整代码
import { useState, useEffect, useRef } from 'react'; import { Engine, Runner, World, Bodies, Composite, Events } from 'matter-js'; const Bubbles = ({ coins }) => { // 持久化核心实例,避免每次渲染重建 const engineRef = useRef(null); const runnerRef = useRef(null); const canvasRef = useRef(null); const ctxRef = useRef(null); const bubblesRef = useRef([]); // 图片缓存,避免重复创建Image对象 const imageCacheRef = useRef({}); // 组件挂载时初始化一次核心资源 useEffect(() => { const winwidth = window.innerWidth; const winheight = window.innerHeight - 80; // 创建引擎和世界 const engine = Engine.create(); const world = engine.world; engineRef.current = engine; // 创建Runner(单独保存实例,方便后续停止) const runner = Runner.create(); Runner.run(runner, engine); runnerRef.current = runner; // 创建Canvas并挂载到DOM const canvas = document.createElement('canvas'); canvas.width = winwidth; canvas.height = winheight; document.querySelector('.bubbles').appendChild(canvas); canvasRef.current = canvas; ctxRef.current = canvas.getContext('2d'); // 监听引擎更新,自定义渲染 const afterUpdateHandler = () => { const ctx = ctxRef.current; if (!ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); bubblesRef.current.forEach(({ bubble, coin }) => { const pos = bubble.position; const size = coin.size; const text = coin.symbol; const priceChange = coin.price_change_percentage_24h.toFixed(1); // 从缓存取图片,没有则创建并缓存 let image = imageCacheRef.current[coin.image]; if (!image) { image = new Image(); image.src = coin.image; imageCacheRef.current[coin.image] = image; } // 设置填充和描边样式 if (priceChange > 0) { ctx.fillStyle = 'rgba(5, 222, 5, 0.15)'; ctx.strokeStyle = 'rgba(5, 222, 5, 0.6)'; } else if (priceChange < 0) { ctx.fillStyle = 'rgba(166, 53, 53, 0.15)'; ctx.strokeStyle = 'rgba(166, 53, 53, 0.8)'; } else { ctx.fillStyle = 'rgba(255, 255, 89, 0.069)'; ctx.strokeStyle = 'rgba(187, 231, 11, 0.443)'; } // 绘制气泡背景 ctx.beginPath(); ctx.arc(pos.x, pos.y, size / 2, 0, 2 * Math.PI); ctx.fill(); ctx.stroke(); // 绘制图片(确保图片加载完成) if (image.complete) { ctx.drawImage( image, pos.x - size / 5, pos.y - size / 2.5, size / 2.5, size / 2.5 ); } // 绘制文本 ctx.font = `${size / 6}px Acme`; ctx.fillStyle = 'white'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(text, pos.x, pos.y + size / 6.5); ctx.font = `${size / 6.5}px Acme`; ctx.fillStyle = 'white'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(`${priceChange}%`, pos.x, pos.y + size / 3); }); }; Events.on(engine, 'afterUpdate', afterUpdateHandler); // 组件卸载时彻底清理资源 return () => { // 移除事件监听 Events.off(engine, 'afterUpdate', afterUpdateHandler); // 停止Runner Runner.stop(runner); // 清空物理世界 Composite.clear(world); // 销毁引擎 Engine.clear(engine); // 移除Canvas canvas.remove(); // 清空图片缓存 imageCacheRef.current = {}; }; }, []); // 空依赖,仅初始化一次 // 当coins变化时,更新物理体 useEffect(() => { if (!engineRef.current) return; const engine = engineRef.current; const world = engine.world; const physics = { frictionAir: 0, friction: 0.2, frictionStatic: 0.2, restitution: 0.1, inertia: Infinity, density: 0.8, }; // 移除旧的气泡物理体 Composite.remove(world, bubblesRef.current.map(item => item.bubble)); // 创建新的气泡物理体 const newBubbles = coins.map(coin => { const x = Math.random() * window.innerWidth; const y = Math.random() * window.innerHeight; const bubble = Bodies.circle(x, y, coin.size / 2, { ...physics, render: { fillStyle: 'transparent' }, }); Composite.add(world, bubble); return { bubble, coin }; }); bubblesRef.current = newBubbles; }, [coins]); // 仅在coins变化时更新 return <></>; }; export default Bubbles;
关键改动说明
- 用useRef持久化实例:引擎、Runner、Canvas、上下文、气泡数组、图片缓存都通过useRef保存,组件生命周期内只创建一次。
- 分离初始化与更新逻辑:初始化逻辑放在空依赖的useEffect,仅执行一次;coins变化时只更新物理体,不重建整个环境。
- 图片缓存:用
imageCacheRef缓存已创建的Image对象,避免每次渲染都新建,减少内存占用。 - 正确清理资源:移除
afterUpdate事件监听,停止Runner实例(原代码错误地停止engine),清空物理世界,销毁引擎,移除Canvas。 - 移除冗余的Matter Render:你自己用Canvas实现了渲染,Matter自带的Render完全多余,直接删除减少性能消耗。
内容的提问来源于stack exchange,提问作者FakeHuman
相关产品推荐
相关产品推荐

