重构 DraggableEntry 、修正卡片高度对齐#1245
Conversation
31d8c2f to
37387f8
Compare
| {React.cloneElement(children, { | ||
| ref: setNodeRef, | ||
| style, | ||
| ...attributes, | ||
| })} |
There was a problem hiding this comment.
使用 React.cloneElement 直接传递 ref 可能会覆盖子组件已有的 ref。虽然当前场景中 Card 组件没有使用 ref,但如果未来子组件发生变化或需要自己的 ref,这种实现方式会导致问题。
ScriptTable.tsx 中的 DraggableRow 使用了 composeRefs 来合并多个 ref(见 ScriptTable.tsx:107-117, 131),这是更安全的做法。建议在这里也采用同样的模式,以提高代码的健壮性和一致性。
There was a problem hiding this comment.
某AI说不需要。现时实际使用也不需要。需要时再加回 composeRefs 吧
There was a problem hiding this comment.
根据AI指示,还原至原有的 composeRefs / mergeRefs 做法 df0c8b1
| ...attributes, | ||
| })} |
There was a problem hiding this comment.
添加了 attributes 的传递,这是一个很好的改进!attributes 包含了可访问性相关的 ARIA 属性(如 role、aria-* 等),有助于提升拖拽功能的无障碍支持。
不过需要注意,ScriptTable.tsx 中的 DraggableRow 并未使用 attributes(见 ScriptTable.tsx:124,143)。为了保持代码库的一致性,建议也在 ScriptTable.tsx 中添加 attributes 的支持。
There was a problem hiding this comment.
不用。這PR只搞 DraggableEntry
| const style = { | ||
| // ScriptCard 移位渉及多个元件上下左右移动,DragEnd时不要使用 dnd-kit 提供的效果 | ||
| ...children.props.style, // Merge existing styles | ||
| transform: CSS.Transform.toString(transform), | ||
| transition, | ||
| opacity: isDragging ? 0.5 : 1, | ||
| zIndex: isDragging ? 10 : "auto", | ||
| }; |
There was a problem hiding this comment.
这里合并了 children.props.style 和拖拽相关的样式,但需要注意对象展开的顺序。当前实现中,如果 Card 组件的 style 中定义了 transform、transition、opacity 或 zIndex,这些值会被拖拽样式覆盖。
从当前使用场景看(Card 组件没有设置这些属性),这个顺序是正确的。但如果未来 Card 的样式发生变化,需要注意可能的样式冲突。
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| zIndex: isDragging ? 10 : "auto", | ||
| }; | ||
|
|
||
| const ref = (children as any).ref; |
There was a problem hiding this comment.
一般我们用的是 <div ref={setNodeRef}> ... </div>
但现在我们是直接用ArcoDesign的Card 做这个 div
所以 ArcoDesign的Card (即 children ) 的 ref function 要先抽出来,再和 setNodeRef 合并, 然后在 React.cloneElement 那边塞回合并后的 ref function.
就算 Card 没有加 ref function, 也不会有问题
| const DraggableEntry = React.forwardRef<HTMLDivElement, DraggableEntryProps>(({ recordUUID, ...rest }, ref) => { | ||
| const sortable = useSortable({ id: recordUUID }); | ||
| const { setNodeRef, transform, transition, listeners, setActivatorNodeRef, isDragging } = sortable; | ||
| const DraggableEntry = ({ recordUUID, children }: DraggableEntryProps) => { |
There was a problem hiding this comment.
修正卡片高度对齐呀
dnd-kit 加了一层 wrapper 的话布局会错误
这PR 1樓 那两个图的分别
|
@CodFrm之前我有問過AI是不是用forwardRef才對。它說不是 不過我也不是很懂這些 |
不然你傳的是一個已生成的ref |
This reverts commit 55377c3.
|
重新再看了一次 我们来重新梳理一下 React.cloneElement 和 forwardRef 在 dnd-kit 拖拽场景里的区别,以及为什么你的代码选择 cloneElement 是正确的。 一、React.cloneElement 的核心作用React.cloneElement 的作用非常明确: 关键点在于:
代码示例: const original = <div className="old-class">内容</div>;
const cloned = React.cloneElement(original, {
className: "new-class",
ref: someRef,
});结果就是新的 div,className 被覆盖,ref 也被设置,原对象不变。 二、在 dnd-kit 场景中为什么必须用它你的组件本质上是一个“行为增强器”:
所以你写了: const mergedRef = composeRefs(setNodeRef, children.ref);
return React.cloneElement(children, {
...attributes,
ref: mergedRef,
style: { ...children.props.style, ...style },
});这三步做到了:
三、如果用
|
| 需求 | forwardRef 是否能解决 | cloneElement + composeRefs 是否能解决 |
|---|---|---|
| 父组件传 ref 给内部 DOM | 是 | 否 |
| 读取 children 已有的 ref | 否 | 是 |
| 合并两个 ref 并覆盖回去 | 否 | 是 |
| 不增加额外 DOM 层 | 通常会增加 | 是 |
| 适合 dnd-kit 这种场景 | 不适合 | 非常适合 |
七、结论
你的场景是典型的“无 DOM 行为注入”:
- 不产出额外节点
- 增强 children 的能力
- 保留使用者原有的 ref 和 props
React 生态里这种模式非常常见(Radix UI、Headless UI、很多拖拽库的 wrapper 都这么做)。
cloneElement + ref 合并 是目前最干净、最符合需求的解法。
forwardRef 在这里不但帮不上忙,反而会让 ref 指向错误的方向。
|
大概明白你的想法,如果追求干净,这样透传实现没有问题 不过我刚刚又看了一下这块的逻辑,其实根本不需要处理ref,这个组件其实是为了抽离Card的拖拽逻辑写的,当时应该是从Table那边复制过来的,Table那边也不用 |
|
ArcoDesign.Card 没有使用 ref 删除拖拽组件ref逻辑 可以. 没问题 如果不是Card,改成其他有用到 ref 的元件的时候再算 |
|
Table 那边的 DraggableContainer 是直接生成一个 tbody 出来 Card 这个不做一层元素,直接用 Card 元素,所以只好这样搞 Table 那边不改了。它能跑没事就算 |
Before
After
div 移除了原来它会自己做一层 div 所以不用再做一层重构 DraggableEntry 、修正卡片高度对齐