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

React函数组件中如何避免列表输入框因重渲染失去焦点

解决React列表项输入框修改时失去焦点的问题

问题描述

我有一个需要渲染的列表项集合(openItems),每个列表项都配有输入框,用于修改对应项的duration属性。但每次修改属性时,父组件(openItems)及其所有子组件(包括输入框)都会重渲染,导致输入框失去焦点。尝试过使用React.memo,但由于数据确实发生了变化,并未起到预期效果,希望了解React函数组件中正确的实现方案。

当前简化代码(Antd组件)

const Page = () => {
  const dispatch = useDispatch();
  const openItems = ...get items from state

  const setMs = (id: string, ms: number) => {
    dispatch(changeDuration({ id, duration: ms }));
  }

  const InputMs: React.FC<{ row: RowInterface }> = (props) => (
    <InputNumber
      defaultValue={props.row.durationMs}
      onChange={(v) => setMs(props.row.id, v)}
    />
  )

  const RowContent: React.FC<{ row: RowInterface }> = (props) => (
    <>
      <SmallText>Duration in ms:</SmallText>
      <InputMs row={props.row} />
    </>
  );

  const Rows: React.FC<RowProps> = (props) => {
    return (
      <>
        {props.data.map((row) =>
          <RowContent row={row} />
        )}
      </>
    )
  };

  return( <Rows data={openItems} /> )
};

export default Page;

已尝试的方案

将输入框拆分为独立组件,但问题依旧:

const InputMs: React.FC<{ row: RowInterface  }> = (props) => {
  const dispatch = useDispatch();
  const setMs = (id: string, ms: number) => {
    dispatch(changeDuration({ id, duration: ms }));
  }
  return (
    <InputNumber
      defaultValue={props.row.durationMs}
      onChange={(v) => setMs(props.row.id, v)}
    />
  )
}

const Page = () => {
  const openItems = ...get items from state;
  const RowContent: React.FC<{ row: RowInterface  }> = (props) => (
    <>
      <SmallText>Duration in ms:</SmallText>
      <InputMs row={props.row} />
    </>
  );

  const Rows: React.FC<RowProps> = (props) => {
    return (
      <>
        {props.data.map((row) =>
          <RowContent row={row} />
        )}
      </>
    )
  };

  return (<Rows data={openItems} />)
};

export default Page;

解决方案

核心思路是稳定组件类型、props引用,同时让React精准识别需要更新的列表项,具体步骤如下:

1. 提取子组件到父组件外部并使用React.memo

将InputMs、RowContent、Rows组件从Page内部移出,并用React.memo包裹。内部定义组件会导致每次父组件重渲染时创建全新的组件类型,触发子组件强制重渲染。

2. 给列表项添加唯一key

map渲染列表时,必须为每个项添加唯一的key(建议用数据项的id),让React能识别每个列表项的身份,仅重渲染变化的项。

3. 用useCallback稳定回调函数引用

回调函数如果直接定义在父组件中,每次重渲染都会生成新的引用,导致React.memo失效。用useCallback包裹回调,确保引用稳定。

4. 用value而非defaultValue绑定数据

defaultValue仅设置初始值,状态更新后不会同步到输入框。改用value绑定row.durationMs,确保输入框值与状态同步,避免重渲染时的值重置。

修改后的完整代码

import React, { memo, useCallback } from 'react';
import { InputNumber } from 'antd';
import { useDispatch } from 'react-redux';

// 独立组件+memo优化
const InputMs = memo(({ row, onDurationChange }) => {
  return (
    <InputNumber
      value={row.durationMs}
      onChange={(v) => onDurationChange(row.id, v)}
    />
  );
});

const RowContent = memo(({ row, onDurationChange }) => {
  return (
    <>
      <SmallText>Duration in ms:</SmallText>
      <InputMs row={row} onDurationChange={onDurationChange} />
    </>
  );
});

const Rows = memo(({ data, onDurationChange }) => {
  return (
    <>
      {data.map((row) => (
        <RowContent
          key={row.id} // 添加唯一key
          row={row}
          onDurationChange={onDurationChange}
        />
      ))}
    </>
  );
});

const Page = () => {
  const dispatch = useDispatch();
  const openItems = ...get items from state;

  // useCallback稳定回调引用
  const onDurationChange = useCallback((id, ms) => {
    dispatch(changeDuration({ id, duration: ms }));
  }, [dispatch]);

  return <Rows data={openItems} onDurationChange={onDurationChange} />;
};

export default Page;

效果说明

修改后,只有被修改的列表项会触发重渲染,其他项保持稳定;输入框在修改过程中不会失去焦点,同时值与状态实时同步。

内容的提问来源于stack exchange,提问作者1jss

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 14:39:12