리액트와 리덕스로 개발하다 보니 이런 상황을 자주 마주쳤습니다.
// Redux 상태를 가져옴
const userDataState = useAppSelector(selectUserState);
useEffect(() => {
// Redux 액션 디스패치
dispatch(getUserAction())
.unwrap()
.then(() => {
// 데이터가 바뀌었을 거라 기대하지만...
console.log('userDataState:', userDataState); // 여전히 초기값(null)이 출력됨 😱
});
}, []);
위 코드에서 저는
getUserAction
이 성공적으로 완료된 후 업데이트된userDataState
를 콘솔에서 보길 기대했습니다. 하지만 실제로는 변경 전의 값(보통 초기값인 null)이 출력됩니다. (더 정확히는 리듀서에서 loading 값이 false로 나옵니다. 일단 값이 안들어갔다고 생각하시면 됩니다.)
"클로저" 때문입니다 클로저가 뭐지? 클릭
클로저를 이해하는데 일주일이 넘게 걸리고 분명 클로저에 대해 저렇게 개념정리를 했는데도 늘 새롭습니다. 아직도 멀었지만 개발을 하면 할 수 록 제 학습력이 평균 이하임을 느낍니다. 학습력이 딸리니, 학습량을 평균이상으로 가져가야겠습니다.
자바스크립트의 클로저(Closure)란 함수가 자신이 생성된 환경의 변수를 기억하는 특성입니다.
이 코드에서:
.then(() => { ... })
콜백 함수가 생성되는 시점에 userDataState
는 초기값(null)입니다.userDataState
값을 "기억"합니다.userDataState
참조는 여전히 초기값을 가리킵니다.제가 개발 중인 로그인 프로세스에서 이러한 문제가 발생했습니다:
dispatch(getUserAction())
.unwrap()
.then(() => {
console.log('userDataState from getUserAction', userDataState); // null이 출력됨
dispatch(initUserAction())
.unwrap()
.then(() => {
console.log('userDataState from initUserAction', userDataState); // 여전히 null 출력됨
// 이후 로직...
});
});
이 문제로 인해:
방법 중 하나는 Redux 상태를 참조하지 않고, 액션의 결과를 직접 사용하는 것입니다:
dispatch(getUserAction())
.unwrap()
.then((userResult) => {
console.log('User data from action result:', userResult); // 여기서는 최신 데이터 접근 가능
// 다음 액션으로 필요한 값 전달
return dispatch(initUserAction()).unwrap().then(() => userResult.userId);
})
.then((userId) => {
// 전달받은 값으로 작업
dispatch(fetchProducts({ userId: Number(userId) }));
});
Promise 체인을 사용하여 각 단계의 결과를 다음 단계로 전달합니다:
dispatch(getUserAction())
.unwrap()
.then(() => {
const userId = localStorage.getItem('userId');
if (!userId) throw new Error('사용자 ID를 찾을 수 없습니다.');
// 이 값을 다음 단계로 전달
return dispatch(initUserAction()).unwrap().then(() => userId);
})
.then((userId) => {
// 전달받은 userId 사용
return dispatch(fetchProducts({ userId: Number(userId) }));
})
.then(() => {
// 최종 성공 처리
message.success('로그인에 성공했습니다.');
navigate('/admin');
});
리덕스 스토어에 직접 접근하여 최신 상태를 가져올 수도 있습니다:
import { store } from '../path/to/store';
dispatch(getUserAction())
.unwrap()
.then(() => {
// 스토어에서 최신 상태 직접 가져오기
const currentState = store.getState();
console.log('Current user state:', currentState.user);
// 이후 로직...
});
클로저의 작동 방식 이해하기: 비동기 콜백에서 외부 변수를 참조할 때 클로저 함정에 주의하자.
상태 의존성 최소화하기: 비동기 액션 체인에서는 이전 단계의 결과를 직접 다음 단계로 전달하자.
필요시 최신 상태 직접 접근하기: 꼭 필요하다면 스토어에서 최신 상태를 직접 가져오는 방법을 사용하자
디버깅 전략 세우기: 상태 변화를 디버깅할 때는 컴포넌트 리렌더링이나 useEffect가 실행되는 시점에 로그를 확인하자.
- 클로저 좋다.
- 하지만 비동기 환경에서 명심하자.
- 특히 상태 관리에서.