React로 테트리스 구현중에 발생한 문제로 자바스크립트에 대한 이해가 부족한게 많이 느껴졌다.
문제 상황을 간단히 설명하면
UsePlayer라는 Custom Hook에서
State : Player의 x,y 를 가지고 있으며
- 1초마다 y축으로 -1씩 이동하거나
- 방향키 입력에 따라 x,y 이동을 구현하는데 두 가지 입력에 따른 player의 위치를 자연스럽게 변경하려 했다.
const onkeydown = (e) => {
switch (e.key) {
case "ArrowLeft":
isPossibleMove(-1, 0);
break;
case "ArrowRight":
isPossibleMove(1, 0);
break;
case "ArrowUp":
break;
case "ArrowDown":
isPossibleMove(0, -1);
break;
}
};
useEffect(() => {
if (playerState === GLOBAL.PLAYER_MOVE) {
isPossibleMove(0, -1);
}
window.addEventListener("keydown", onkeydown);
return () => {
window.removeEventListener("keydown", onkeydown);
};
}, [time]);
isPossibleMove에서 Player의 위치 State값을 변경 해주고 있으며, time에 따른 y축이동, keydown 이벤트 발생 시 이동은 되나, 같은 렌더링 실행 context에서 두가지 상태변화가 모두 일어나는 경우 방향키 좌우 입력 시 time에 따른 y축 이동은 무시되는 현상이 발생하고 있다.
useEffect은 렌더링 이후에 비동기적으로 수행되는데 자바스크립트의 이벤트 루프 동작에서 보면 DOM관련 이벤트핸들러인 keydown 이벤트는 call stack에 쌓인 일반 함수들이 모두 실행된 이후에 실행되기 때문에 코드에서 보면
useEffect안의 isPossibleMove( 0, -1 )가 실행되어 내부에서 현재 y 위치에서 -1 만큼 값을 변경시키려 하였으나,
onkeydown에서 isPossibleMove(1, 0)이나 (-1, 0)이 실행되면 이전 y 위치에서 변화량은 없고 x 위치만 변경시키므로 결과적으로 x축으로만 이동하는 모습을 보여주게 된다.
그리고 또한 useEffect에 dependency array에 time값만 설정되어 있어서 key입력 시 바로바로 안바뀔거 같으니 이 부분도 수정해보겠다.
useEffect(() => {
if (playerState === GLOBAL.PLAYER_MOVE) {
isPossibleMove(0, -1);
}
}, [time]);
useEffect(() => {
window.addEventListener("keydown", onkeydown);
return () => {
window.removeEventListener("keydown", onkeydown);
};
}, [/*position*/]);
주석 부분을 보면 dependency array로 position을 넣으니 정상동작은 한다. 그런데 position이 없을 때의 동작이 뭔가 이해가 가질 않았다.
처음에 그대로 두면 시간에 따라 y가 1씩 내려오다가 keydown 이벤트를 발생 시키면 처음 위치로 계속 이동된다는 것이였다. 결론을 바로 이야기 하면 함수의 closure라는 특성 때문이다.
function usePlayer() { <- 상위함수
const [positionState, setPositionState] = useState({x:0, y:0});
function isPossibleMove() {
setPositionState( ... )
}
function onkeydown(){ <-- 하위함수
isPossibleMove()
}
}
useEffect의 윗부분은 대략 이런식으로 되어있다.
useEffect의 dependency array가 없으면 핸들러 함수인 onkeydown이 처음 usePlayer component가 실행됬을때에만 선언되게 되는데 이때 positionState의 초기값을 변수로 가지게 된다. 그리고 이후 keydown 이벤트가 발생했을때 이전 상태값은 항상 초기값인 현상이 발생한다.(closure, lexical environment)
useEffect에 dependency array에 positionState를 추가한다면 positionState의 이전 상태값이 항상 최신 상태값으로 업데이트 되기때문에 우리가 원하는대로 잘 동작하개된다.
'🦈 react' 카테고리의 다른 글
[React로 화이트보드] 상태 변경 시 Canvas 업데이트 (0) | 2024.02.02 |
---|---|
[React로 화이트보드] React에서 Canvas로 선그리기 (0) | 2024.01.09 |
[React로 화이트보드] TypeScript + Tailwindcss 개발환경 셋팅 (2) | 2024.01.06 |
[React로 테트리스] Redux-toolkit으로 전역상태 관리하기 (0) | 2023.12.28 |
[React로 테트리스] 컴포넌트 구조 설계 (1) | 2023.12.18 |