React屏幕键盘组件:如何避免按键不必要重渲染?
解决屏幕键盘组件按键重复渲染问题
问题分析
你创建Key子组件来避免重复代码,但每次按键触发状态变更时,所有按键都会重新渲染,核心原因有两个:
Key组件定义在Keyboard内部,每次Keyboard重渲染都会生成新的组件类型,导致所有Key实例被强制重渲染;- 传递给
Key的内联回调函数(如onMouseDown中的匿名函数)每次渲染都会生成新引用,触发Key的重渲染。
优化方案
方案1:提取并缓存Key组件,稳定回调引用
- 将
Key组件移到Keyboard外部,并用React.memo包裹,实现props浅比较,仅在props变化时重渲染; - 使用
useCallback稳定父组件中的回调函数引用,避免因函数引用变化触发Key重渲染。
修改后的代码:
import { memo, useCallback, useRef } from 'react'; // 提取并缓存Key组件 const Key = memo(({ text, onMouseDown, onMouseUp, onMouseLeave }: { text: string; onMouseDown: () => void; onMouseUp: () => void; onMouseLeave: () => void; }) => { return ( <Button className="text-lg p-0 w-12" variant="outline" onMouseDown={onMouseDown} onMouseUp={onMouseUp} onMouseLeave={onMouseLeave} > {text} </Button> ); }); function Keyboard({ setState }: { setState: React.Dispatch<React.SetStateAction<string>> }) { const keyInterval = useRef<NodeJS.Timeout>(); const keyTimeout = useRef<NodeJS.Timeout>(); // 稳定keyUpBehaviour引用 const keyUpBehaviour = useCallback(() => { clearInterval(keyInterval.current); clearTimeout(keyTimeout.current); }, []); // 稳定keyDownBehaviour引用 const keyDownBehaviour = useCallback((action: () => void) => { action(); keyTimeout.current = setTimeout(() => { keyInterval.current = setInterval(() => { action(); }, 50); }, 500); }, []); // 稳定按键按下的回调逻辑 const handleKeyDown = useCallback((text: string) => { keyDownBehaviour(() => setState(prev => prev + text)); }, [keyDownBehaviour, setState]); return ( <div className="flex flex-col mt-2 gap-2 w-auto"> <div className="flex gap-[0.2rem] sm:gap-2 w-auto"> <Key text="b" onMouseDown={() => handleKeyDown("b")} onMouseUp={keyUpBehaviour} onMouseLeave={keyUpBehaviour} /> <Key text="a" onMouseDown={() => handleKeyDown("a")} onMouseUp={keyUpBehaviour} onMouseLeave={keyUpBehaviour} /> </div> </div> ); }
方案2:将定时器逻辑封装到Key内部(更优)
把每个按键的定时器逻辑移到Key组件内部,每个按键独立维护自己的定时器,父组件仅传递稳定的状态更新回调,进一步减少不必要的重渲染。
修改后的代码:
import { memo, useCallback, useRef } from 'react'; const Key = memo(({ text, appendToState }: { text: string; appendToState: (text: string) => void; }) => { const keyInterval = useRef<NodeJS.Timeout>(); const keyTimeout = useRef<NodeJS.Timeout>(); const keyUpBehaviour = () => { clearInterval(keyInterval.current); clearTimeout(keyTimeout.current); }; const keyDownBehaviour = () => { appendToState(text); keyTimeout.current = setTimeout(() => { keyInterval.current = setInterval(() => { appendToState(text); }, 50); }, 500); }; return ( <Button className="text-lg p-0 w-12" variant="outline" onMouseDown={keyDownBehaviour} onMouseUp={keyUpBehaviour} onMouseLeave={keyUpBehaviour} > {text} </Button> ); }); function Keyboard({ setState }: { setState: React.Dispatch<React.SetStateAction<string>> }) { // 稳定状态更新回调 const appendToState = useCallback((text: string) => { setState(prev => prev + text); }, [setState]); return ( <div className="flex flex-col mt-2 gap-2 w-auto"> <div className="flex gap-[0.2rem] sm:gap-2 w-auto"> <Key text="b" appendToState={appendToState} /> <Key text="a" appendToState={appendToState} /> </div> </div> ); }
优化效果
两种方案都能避免状态变更时所有按键的无意义重渲染:
- 方案1保留了父组件的定时器逻辑,适合需要统一管理定时器的场景;
- 方案2将逻辑内聚到子组件,代码更清晰,且每个按键独立维护定时器,避免了共享定时器可能带来的冲突问题。
内容的提问来源于stack exchange,提问作者Tommaso
相关产品推荐
相关产品推荐

