-
Notifications
You must be signed in to change notification settings - Fork 8
fix: 基础 shape destroy 逻辑 #343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
这个 PR 修复了在更新画布时基础图形没有被正确销毁的问题,并为此添加了详尽的测试用例。代码的改动方向是正确的,但 destroyElement 函数的实现方式引入了新的问题,可能导致在某些场景下(如纯删除或组件卸载)图形无法被销毁。我提出了一些修改建议,以使销毁逻辑更加健壮和通用,从而解决这些潜在的 bug。
| function destroyElement(vNode: VNode | VNode[] | null, nextVNode?: VNode) { | ||
| Children.map(vNode, (node: VNode | null) => { | ||
| if (!node) return; | ||
| const { component, children } = node; | ||
| const { component, children, shape, tag } = node; | ||
| if (component) { | ||
| component.willUnmount(); | ||
| destroyElement(children); | ||
| component.didUnmount(); | ||
| component.destroy(); | ||
| } else { | ||
| } else if (children) { | ||
| destroyElement(children); | ||
| } else if (tag === Shape && shape) { | ||
| // 更新模式时销毁上个节点 | ||
| nextVNode && shape.destroy(); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
当前的 destroyElement 实现虽然解决了更新时基础图形的销毁问题(当提供了 nextVNode 时),但未能正确处理其他销毁场景,这可能会导致 bug 和内存泄漏。
具体来说:
- 纯删除场景:当一个元素被直接移除(而非替换)时,
destroyElement的调用不会传入nextVNode。在这种情况下,nextVNode && shape.destroy()的判断会为 false,导致图形不会被销毁。 - 递归销毁场景:在销毁一个组件时,会递归调用
destroyElement(children)。这个调用同样没有传递nextVNode,因此该组件下的所有基础图形子节点都不会被销毁。
nextVNode 这个参数及其相关检查似乎是不必要的,并且是导致这些问题的根源。无论是在更新还是删除场景下,只要 destroyElement 被调用,其对应的 shape 都应该被销毁。
建议简化该函数,移除对 nextVNode 的依赖,使销毁逻辑在所有情况下都健壮和正确。
这个修改也需要更新在 updateElement 中对 destroyElement 的调用(在第 220 行)。
function destroyElement(vNode: VNode | VNode[] | null) {
Children.map(vNode, (node: VNode | null) => {
if (!node) return;
const { component, children, shape, tag } = node;
if (component) {
component.willUnmount();
destroyElement(children);
component.didUnmount();
component.destroy();
} else if (children) {
destroyElement(children);
} else if (tag === Shape && shape) {
shape.destroy();
}
});
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我也感觉改这里怪怪的,ai写的挺有道理的
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
直接 destroy 会导致 A --> null 这种情况离场动画失效,A --> null 的情况 vnode 会直接变为 null,在 vnode tree 中会被去掉并销毁,目前版本的代码逻辑运行没问题
|
|
||
| const nextVNode = createVNode(parent, nextElement as VNode); | ||
| destroyElement(lastElement); | ||
| destroyElement(lastElement, nextVNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ); | ||
|
|
||
| // 无离场动画效果 | ||
| await canvas.update(nextProps); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个应该要有离场动画才对。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个应该要有离场动画才对。
离场动画在销毁的时候才生效,从 A --> B 这种被当成了更新,会执行 B 的入场动画效果,和现有逻辑一致
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A->B 因为是完全不同的元素,所以走的不是更新吧,我感觉应该是销毁(离场)-> 入场 。
要不看看其他动画库怎么定义的 react motion库
现有逻辑就是你这个pr要改的,不能参考。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
而且没有A的离场但会执行B的入场?不匹配吧
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
现在的逻辑是执行 B 的入场动画,从 A 到 B 的一个渐变过渡效果,如果等 A 的离场执行结束,再执行 B 的入场,过渡效果就没有了,和现在已有的整体效果完全不一样了
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
现在的逻辑不就是这份pr要改的bug吗。。。
A到B定义了离场和入场动画,但只执行入场动画不对吧
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
你放的gif没看懂,是想说是哪个单测的效果呢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
你放的gif没看懂,是想说是哪个单测的效果呢
「而且没有A的离场但会执行B的入场?不匹配吧」现在所有的逻辑都是以 B 入场为准,从 A 到 B 会有个过渡效果,这个 pr 只是修复 bug ,不是增加 feature 啊,不变更已有逻辑
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
你要不本地跑下测试用例或者手动调整代码试下?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
群里直接沟通吧
| property: ['width', 'height'], | ||
| start: { | ||
| width: 0, | ||
| height: 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个单测和你加的逻辑没关系
|
|
||
| await delay(200); | ||
| const { props: nextProps } = ( | ||
| <Canvas context={context}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
增加一个无离场动画的销毁
| function destroyElement(vNode: VNode | VNode[] | null, nextVNode?: VNode) { | ||
| Children.map(vNode, (node: VNode | null) => { | ||
| if (!node) return; | ||
| const { component, children } = node; | ||
| const { component, children, shape, tag } = node; | ||
| if (component) { | ||
| component.willUnmount(); | ||
| destroyElement(children); | ||
| component.didUnmount(); | ||
| component.destroy(); | ||
| } else { | ||
| } else if (children) { | ||
| destroyElement(children); | ||
| } else if (tag === Shape && shape) { | ||
| // 更新模式时销毁上个节点 | ||
| nextVNode && shape.destroy(); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我也感觉改这里怪怪的,ai写的挺有道理的
|
关于 F2 的动画设计,达成几项一致:
|
补充: |
当
Canvas的实例调用render方法完成首次渲染后,再次执行update方法更新画布,如果使用了line | circle | rect | ...等基础图形标签,会导致上一屏的基础图形不会被正确擦除。对于下面测试用例:
rect没有销毁擦除)rect被销毁擦除)