You need to enable JavaScript to run this app. 函数组件Datatable输入框失焦:内联写法正常、内部定义组件为何失效
优惠活动
大模型
产品
解决方案
定价
更多

函数组件Datatable输入框失焦:内联写法正常、内部定义组件为何失效

react-table Datatable输入框失焦问题解析与解决方案

问题背景

在基于react-table封装Datatable组件的实际开发中,会遇到一个非常典型的行为差异:

  • 失效场景:如果把输入类子组件定义在父组件内部,只要在输入框中键入任意字符,输入框就会自动丢失焦点
  • 正常场景:如果将input的JSX代码直接内联写在Datatable的columns配置项中,输入功能可以完全正常运行
  • 已经初步定位的失效方向:每次输入触发父组件状态更新时,父组件会触发重渲染,定义在内部的组件会被重新创建,最终导致失焦

基于这个定位,还有两个核心问题需要明确:

  1. 为什么直接写内联JSX的写法,完全不会出现失焦问题?
  2. 如何在不把输入代码硬编码在Datatable配置项的前提下,让内部定义组件的写法实现正常输入?

失效场景代码示例

import Datatable from "react-table";
import { useState } from "react";
const Test = () => {
  const [first, setFirst] = useState("");
  const [second, setSecond] = useState("");

  const Field1 = () => (
    <input
      defaultValue={first}
      onChange={(e) => setFirst(e.currentTarget.value)}
    />
  );

  const Field2 = () => (
    <input
      defaultValue={second}
      onChange={(e) => setSecond(e.currentTarget.value)}
    />
  );

  return (
    <Datatable
      noText
      pageSize={1}
      showPagination={false}
      data={[{ userName: "asdas", email: "asdsad" }]}
      columns={[
        {
          Header: "www",
          accessor: "userName",
          Cell: <Field1 />
        },
        {
          Header: "Email",
          accessor: "email",
          Cell: <Field2 />
        }
      ]}
    />
  );
};

export default Test;

正常场景代码示例

import Datatable from "react-table";
import { useState } from "react";
const Test = () => {
  const [first, setFirst] = useState("");
  const [second, setSecond] = useState("");

  return (
    <Datatable
      noText
      pageSize={1}
      showPagination={false}
      data={[{ userName: "asdas", email: "asdsad" }]}
      columns={[
        {
          Header: "www",
          accessor: "userName",
          Cell: (
            <input
              defaultValue={first}
              onChange={(e) => setFirst(e.currentTarget.value)}
            />
          )
        },
        {
          Header: "Email",
          accessor: "email",
          Cell: (
            <input
              defaultValue={second}
              onChange={(e) => setSecond(e.currentTarget.value)}
            />
          )
        }
      ]}
    />
  );
};

export default Test;

核心问题解答

1. 内联JSX写法不会失焦的底层原因

这个差异本质上是由React的DOM Diff协调机制决定的,和受控/非受控组件的写法没有关系:

  • 第一种失效写法里,每次父组件重渲染,都会生成一个全新的函数组件类型Field1/Field2。React在做组件树对比时,只要发现节点的类型和上一次渲染不一致,就会直接卸载旧的DOM节点,挂载全新的input节点。新挂载的节点根本没有之前的聚焦状态,自然输入一个字符就会失焦。
  • 第二种内联写法里,columns配置项的Cell属性存储的是已经渲染完成的原生input的React元素。每次父组件重渲染生成的新input元素,节点类型始终是固定的原生input,React在做Diff时会执行原地更新逻辑:只会修改节点的属性、文本内容,不会卸载重建DOM节点,因此可以完整保留输入框的聚焦状态。

2. 非硬编码场景的可行实现方式

不需要把输入逻辑硬写在columns配置里,只要保证传给react-table的组件引用稳定,就可以避免重渲染时的组件重建,常见的实现方案有三种:

方案一:将输入子组件定义移到父组件外部

只要组件定义不放在父组件的渲染函数内部,组件的引用就是全局固定的,不会随父组件重渲染重新创建。原本组件需要用到的父组件状态、状态修改方法,通过props传入即可:

// 组件定义在父组件外部,引用全局固定
const Field1 = ({ value, onChange }) => (
  <input
    defaultValue={value}
    onChange={(e) => onChange(e.currentTarget.value)}
  />
);
const Field2 = ({ value, onChange }) => (
  <input
    defaultValue={value}
    onChange={(e) => onChange(e.currentTarget.value)}
  />
);

const Test = () => {
  const [first, setFirst] = useState("");
  const [second, setSecond] = useState("");

  return (
    <Datatable
      noText
      pageSize={1}
      showPagination={false}
      data={[{ userName: "asdas", email: "asdsad" }]}
      columns={[
        {
          Header: "www",
          accessor: "userName",
          Cell: <Field1 value={first} onChange={setFirst} />
        },
        {
          Header: "Email",
          accessor: "email",
          Cell: <Field2 value={second} onChange={setSecond} />
        }
      ]}
    />
  );
};

方案二:使用useMemo缓存渲染好的输入元素

如果因为作用域依赖的原因,必须把输入逻辑写在父组件内部,可以把渲染好的输入元素用useMemo缓存,避免每次渲染生成全新的组件类型引用:

import { useMemo } from "react";

const Test = () => {
  const [first, setFirst] = useState("");
  const [second, setSecond] = useState("");

  // 缓存生成的输入元素,依赖项变化时才会重新生成
  const field1El = useMemo(() => (
    <input
      defaultValue={first}
      onChange={(e) => setFirst(e.currentTarget.value)}
    />
  ), [first]);
  const field2El = useMemo(() => (
    <input
      defaultValue={second}
      onChange={(e) => setSecond(e.currentTarget.value)}
    />
  ), [second]);

  return (
    <Datatable
      noText
      pageSize={1}
      showPagination={false}
      data={[{ userName: "asdas", email: "asdsad" }]}
      columns={[
        {
          Header: "www",
          accessor: "userName",
          Cell: field1El
        },
        {
          Header: "Email",
          accessor: "email",
          Cell: field2El
        }
      ]}
    />
  );
};

方案三:利用react-table的Cell渲染函数特性

react-table的Cell配置项本身支持传入接收单元格信息的渲染函数,只要用useCallback固定这个函数的引用,函数内部的抽离逻辑不会触发组件类型变化,非常适配需要动态渲染单元格的业务场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.05 16:45:07