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

React屏幕键盘组件:如何避免按键不必要重渲染?

解决屏幕键盘组件按键重复渲染问题

问题分析

你创建Key子组件来避免重复代码,但每次按键触发状态变更时,所有按键都会重新渲染,核心原因有两个:

  1. Key组件定义在Keyboard内部,每次Keyboard重渲染都会生成新的组件类型,导致所有Key实例被强制重渲染;
  2. 传递给Key的内联回调函数(如onMouseDown中的匿名函数)每次渲染都会生成新引用,触发Key的重渲染。

优化方案

方案1:提取并缓存Key组件,稳定回调引用

  1. 将Key组件移到Keyboard外部,并用React.memo包裹,实现props浅比较,仅在props变化时重渲染;
  2. 使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:35:55