막상 사용성과 재미를 위해 캔버스 요소들에 대해서 충돌 방지를 달아놨는데, 문제는 dragmove이벤트는 매 프레임마다 실행하기 때문에 1초에 수십, 수백번이 이런남에도 불구하고 이 모든 프레임마다 충돌 방지 스크립트를 실행하기 때문에 노드가 많아질 수록 점점 프레임 드랍이 일어나는 듯한 느낌이 들었다.
따라서 이를 개선하기 위해 여러 가지 방법을 시도해보았다.
Zustand
는 최근에 인기가 많아지고 있는 전역 상태 라이브러리로, selector 패턴을 사용하면 데이터가 바뀌는 부분만 렌더링이 가능하다는 장점을 가지고 있다.
화면 기록 2024-11-14 오후 11.27.57.mov
나는 useEffect에 data가 바뀔 때마다 checkCollision을 실행할 수 있도록 했다.
Zustand
는 상태 관리를 간결하게 하고 전역 상태를 쉽게 관리할 수 있지만, 특정한 성능 최적화가 이루어지지 않은 상태에서 빈번하게 상태를 업데이트할 경우 성능 문제가 발생할 수 있다고 한다
특히 useEffect
가 의존성 배열을 통해 data
변경을 감지하여 checkCollision
을 호출할 때, Zustand
의 store
에서 데이터를 가져와 업데이트하는 과정에서 추가적인 메모리와 연산이 요구될 수 있기 때문에 이러한 과정 자체가 오히려 빠르고 반복적으로 호출되는 상황에서는 오버헤드로 작용할 수 있다.
반면 Context API
는 기본적으로 React
의 memoization
최적화를 통해 상태 변경을 효율적으로 관리하며, 컴포넌트 트리에서 관련된 부분에 대해서만 변경을 감지하고 리렌더링을 수행하기 때문에 더 빠르게 반응할 수 있다고 생각하여 다시 Conetext API
를 이용한 방식으로 롤백하기로 했다.
Context API
를 활용한 방식으로 회귀한 뒤, 어떻게 다른 방식으로 조금 더 렌더링을 최적화 할 수 있는 방식이 있을까 생각해보았다.
현재 우리는 각각의 노드에 대해 움직이는 모션과 충돌하는 로직을 넣기 위해 지금까지 이렇게 고생을 해왔다. 이 모든 것은 충돌하는, 움직이는 듯한 애니메이션을 위해서였다. 따라서 requestAnimationFrame을 활용하면 조금이라도 더 자연스러워지지 않을까 생각했다.
export function useCollisionDetection(nodeData: NodeData, updateNode: (id: number, updates: Partial<Node>) => void) {
const layer = useRef<Konva.Layer>(null);
const handleCollision = useCallback(
(base, target) => {
const newTargetPosition = moveOnCollision(target, base);
updateNode(target.attrs.id, { location: newTargetPosition });
},
[nodeData, updateNode],
);
const detectCollisions = useCallback(
(layer: Konva.Layer) => {
const nodes = layer.children.filter((child) => child.attrs.name === "node");
nodes.forEach((base) => {
nodes.forEach((target) => {
if (base !== target) {
if (isCollided(base.getClientRect(), target.getClientRect())) {
handleCollision(base, target);
}
}
});
});
},
[handleCollision],
);
useEffect(() => {
if (layer.current) {
requestAnimationFrame(() => {
detectCollisions(layer.current);
});
}
}, [layer, detectCollisions]);
return layer;
}
충돌을 감지하는 로직을 따로 분리해도 좋을 것 같아서 커스텀 훅으로 분리해보았다. 기본 로직은 똑같으며, layer라는 ref를 넘겨주어 캔버스의 layer에 ref로 연결하는 방식이다.
여기서 달라진 점이라고는 RequestAnimationFrame을 적용한 점이다.