오늘은 저번시간 구현한 history 스택에 저장되는 데이터를 가지고 Undo, Redo 구현과 지우는 문제를 구현하기 위해
Remove기능을 만들고 퍼즐에 수정 횟수를 제한하여 문제풀이가 가능하도록 만들어보겠습니다.
오늘도 가보자~!
개인적인 공부를 위해 작성하는 블로그입니다. 혹시라도 잘못되거나 부족한 부분이 있다면 댓글로 알려주시면 감사하겠습니다.
💡 핵심 기능 구현
- 성냥개비 드래그 & 드롭 (완료)
- 성냥개비 회전 (완료)
- Match 컴포넌트 분리, History 기능 (완료)
- (new) Undo, Redo, Remove (오늘 할 것)
- 퍼즐 정답 검증 시스템
💁🏻 Undo, Redo 구현
지금 히스토릴 배열에 변경된 내용을 저장하고 현재 스탭을 currentStep으로 가리키고 있는데 undo()는 currentStep -1의 내용을 가져와 id를 확인해 before에 있는 x, y, angle 값으로 변경하고 redo()는 반대로 구현하였다.
const undo = () => {
if (currentStep >=0) {
const { id, before } = history[currentStep]
setMatchsticks((prev) =>
prev.map((stick) =>
stick.id === id ? {...stick , ...before} : stick
)
)
setCurrentStep(currentStep - 1); // 이전 단계로 이동
}
}
const redo = () => {
if (currentStep < history.length - 1) {
const { id, after } = history[currentStep + 1]
setMatchsticks((prev) =>
prev.map((stick) =>
stick.id === id ? {...stick, ...after} : stick
)
)
setCurrentStep(currentStep + 1)
}
}
잘 동작했는데 이미지 포지션 기준점과 회전중심이 달라 회전상태 변경으로 포지션이 달라지기 때문에 현재 변경된 부분만 반영하도록 한 히스토리에서 회전 시 변경된 회전값만 저장하고 있어 다음과 같이 회전 후 Undo 했을 때 포지션이 바뀌는 문제가 생겼다.
이를 해결하려면 회전 시 바뀐 x, y값도 저장하거나 이미지 기준점을 회전 중심축과 동일하게 설정하면 된다고 생각했는데 첫 번째 방법은 비효율적일 것 같아 두 번째 방법인 기준점을 변경하는 방법을 찾아보니 offset 옵션을 통해 설정할 수 있다고 한다.
<Image
id={stick.id}
x={stick.x}
y={stick.y}
rotation={stick.angle}
width={18}
height={150}
offset={{
x: 9, // 이미지 폭의 절반
y: 75, // 이미지 높이의 절반
}}
image={image}
draggable
onTap={() => onSelect(stick.id)} // 모바일 지원
onClick={() => onSelect(stick.id)} // 선택
onDragEnd={(e) => onDragEnd(e, stick.id)}
onTransformEnd={(e) => onTransformEnd(e.target.rotation(), stick.id)}
/>
👨🏻💻 Remove 구현
이걸 어떻게 구현할까 고민을 해봤는데 검색을 하기 전에 그냥 떠오르는 생각으로는 지웠다는 상태를 나타내는 방법으로 성냥개비 구조체에 상태를 하나 만들거나 x , y 가 -1로 지워진 상태를 표시하는 것도 가능할 것 같다. 우선 isDeleted 플래그를 사용하는 방식으로 구현했다.
const remove = () => {
if (selectedMatchstick) {
const select = matchsticks.find((stick) => stick.id === selectedMatchstick)
setMatchsticks((prev) =>
prev.map((stick) =>
stick.id === selectedMatchstick ? {...stick, isDeleted: true} : stick
)
)
setSelectedMatchstick(null)
const before = {...select}
const after = { ...select, isDeleted: true}
saveState("remove", selectedMatchstick, before, after)
}
}
...(생략)
{matchsticks
.filter((stick) => !stick.isDeleted) // isDeleted 필터
.map((stick) => (
<Matchstick
...(생략)
이렇게 했는데 진짜 이상한 게 Undo가 안 되는 문제가 발생했다..
console창에 확인해 보니 undo를 해도 여전히 isDeleted가 true로 되어있었다. 그렇다면 before에 있는 이전에 select에 담아놓은 객체가 변경되었나 싶어 const deep_copy_select = JSON.parse(JSON.stringify(select))나 structuredClone을 사용해 깊은 복사를 해주었는데도 여전히 같은 문제가 있었다.
그래서 그냥 before에도 isDeleted: false를 넣어주었는데 Undo가 작동했다...
const remove = () => {
if (selectedMatchstick) {
const select = matchsticks.find((stick) => stick.id === selectedMatchstick)
setMatchsticks((prev) =>
prev.map((stick) =>
stick.id === selectedMatchstick ? {...stick, isDeleted: true} : stick
)
)
setSelectedMatchstick(null)
const before = {...select, isDeleted: false}
const after = { ...select, isDeleted: true}
saveState("remove", selectedMatchstick, before, after)
}
}
원인은 그냥 Undo과정에서 예전의 값을 덮어쓰는 방식인데 before에 저장된 isDeleted 값 자체가 없어서 기존에 있던 isDeleted: true가 남아 있어서 그런 거였다..
잘 작동하는 것 같다. 다음 주는 여행으로 한 주 쉬고 다녀와서 횟수제한 정답검증과 전체적인 UI를 손봐야겠다.
다음주에 봐요~!
'개발 > 개발로그' 카테고리의 다른 글
[React] 간단한 성냥퍼즐 웹 서비스 만들기 9일차 (정답 검증 구현) (1) | 2024.12.09 |
---|---|
[React] 간단한 성냥퍼즐 웹 서비스 만들기 8일차 (게임 타입, 횟수 제한 구현) (0) | 2024.12.05 |
[React] 간단한 성냥퍼즐 웹 서비스 만들기 6일차 (리팩토링, History 기능) (0) | 2024.11.24 |
[React] 간단한 성냥퍼즐 웹 서비스 만들기 5일차 (Konva.Transformer) (0) | 2024.11.18 |
[React] 간단한 성냥퍼즐 웹 서비스 만들기 4일차 (react-konva) (3) | 2024.11.15 |