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

使用dnd-kit-sortable-tree时输入框输字符即失焦求助

解决dnd-kit-sortable-tree输入框输入时失焦的问题

问题原因分析

输入框每输入一个字符就失焦,核心原因有两点:

  1. 内联TreeItemComponent触发组件频繁卸载/挂载:你在SortableTree中用内联函数作为TreeItemComponent的取值,每次父组件因state更新重渲染时,这个内联函数会生成新的组件引用。dnd-kit会判定树项组件发生变化,卸载旧的TreeItem实例并挂载新实例,导致输入框失去焦点。
  2. 局部state与全局state不同步:TreeItem内部维护了sample局部state,当全局items更新后,局部state未及时同步,即使手动调用focus也会因为组件重新挂载失效。

修复方案

1. 避免内联传递TreeItemComponent

将TreeItem组件直接传递给TreeItemComponent prop,额外的回调函数通过extraProps传递,确保组件引用稳定,避免频繁重建。

2. 移除TreeItem的局部state

直接使用全局state中的props.item.value作为输入框的值,消除状态不一致问题,同时减少不必要的重渲染。

3. 用useCallback包裹状态更新函数

优化回调函数的引用稳定性,减少父组件重渲染对树项的影响。

修改后的完整代码

import React, { useState, useRef, useCallback } from "react";
import { SimpleTreeItemWrapper, SortableTree } from "dnd-kit-sortable-tree";
import sizeof from "object-sizeof";

export default function NestedSortableTree() {
  const [items, setItems] = useState(initialViableMinimalData);

  console.log(sizeof(items), items, "bytes");

  // 用useCallback包裹,保持函数引用稳定
  const handleItemChange = useCallback((itemId, newValue) => {
    setItems((prevItems) => {
      const updateItems = (nodes) =>
        nodes.map((node) => {
          if (node.id === itemId) {
            return { ...node, value: newValue };
          }
          if (node.children) {
            return { ...node, children: updateItems(node.children) };
          }
          return node;
        });

      return updateItems(prevItems);
    });
  }, []);

  return (
    <SortableTree
      items={items}
      onItemsChanged={setItems}
      TreeItemComponent={TreeItem}
      extraProps={{ onItemChange: handleItemChange }} // 传递额外回调
    />
  );
}

const TreeItem = React.forwardRef((props, ref) => {
  const inputRef = useRef(null);

  const onChange = (event) => {
    const newValue = event.target.value;
    props.onItemChange(props.item.id, newValue);
  };

  return (
    <SimpleTreeItemWrapper {...props} ref={ref} disableCollapseOnItemClick>
      <input
        ref={inputRef}
        className="border"
        value={props.item.value} // 直接使用全局state的值
        onChange={onChange}
      />
    </SimpleTreeItemWrapper>
  );
});

const initialViableMinimalData = [
  {
    id: 1,
    value: "Jane",
    children: [
      { id: 4, value: "John", type: "text" },
      { id: 5, value: "Sally" },
    ],
  },
  {
    id: 2,
    value: "Fred",
    children: [
      {
        id: 6,
        value: "Eugene",
        children: [
          { id: 7, value: "John" },
          { id: 8, value: "Sally" },
        ],
      },
    ],
  },
  { id: 3, value: "Helen" },
];

额外说明

  • 去掉内联组件后,TreeItem的引用保持稳定,dnd-kit不会再频繁卸载/挂载树项,输入框焦点得以正常保留。
  • 直接使用全局state的值,避免了局部state与全局state的同步问题,输入响应更直接流畅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:12:02