어쩌다보니..
혼자 공부할 때 useEffect 언마운트에서 가볍게 스냅샷으로 작동하는 것 같다며 마지막에 언급하고 끝났었다.
그러나 스냅샷보다 훨씬 중요한 무언가가 있었다..
바로 클로저였다.
퀴즈
let count = 1;
function makeCounter() {
count = 0;
return function() {
return ++count;
};
}
count = 10;
let counter = makeCounter();
console.log(counter()); // <-- 뭘까??
정답을 모른다면(글을 읽고 있는 다른 사람이든, 미래의 나든) 밑으로 쭉 내려서 자바스크립트 공식문서를 읽는 편이 좋을듯 하다. 클로저 개념은 필수다!!
일단 퀴즈의 정답은 1이다.
나도 오늘 막 배웠는데, useEffect의 언마운트에서 왜 기묘한 일이 발생하는지 이제야 이해할 수 있었다.
이전의 포스팅은 나만 볼 수 있으니, 여기에 좀 복붙을 해오자면,
특이한 점
import { useEffect, useState } from "react"; const StarRating = ({starCount}) => { const [starClickedNum, setStarClickedNum] = useState(0); const stars = []; // !! 여기부터 중요 !! console.log(starClickedNum); useEffect(() => { const currentTimetoString = new Date().toLocaleString(); return () => { console.log(starClickedNum); console.log(currentTimetoString); console.log(new Date().toLocaleString()); } }, [starClickedNum]) // !! 여기까지 중요 !! for (let i = 0; i < starCount; i++) { stars.push(<img src="/assets/blueStar.png" alt="star" className="w-4 h-4" />); } return ( <div className="flex"> {stars.map((star, index) => ( <div onClick={() =>setStarClickedNum(prev => prev + 1)} key={index}>{star}</div> ))} </div> ) } export default StarRating;
starClickedNum이 바뀌었을 때 첫 번째 줄의 starClickedNum은 업데이트 된 값으로,
클린업 함수 내부의 starClickedNum은 업데이트 이전의 값으로 출력된다.
이런식으로 오히려 값이 다시 작아진다.
useEffect 내부만의 스냅샷이 아닌, 전체 코드의 스냅샷을 기준으로 하는듯 하다.
참고로, new Date()의 경우는 실제 실행 시간을 기준으로 출력된다.
결과적으로는 일단 위에서부터 출력하고, dependency 배열 내부의 값이 바뀌기 이전의 스냅샷을 기반으로 클린업 함수를 실행한다.
이렇게 대충 혼자 이해하고 넘어갔었다.
결론..
내가 이해한 내용이 대충은 맞다. 그래도 원리는 제대로 모르고 억지로 이해했으니 클로저와 렉시컬 환경을 아는 시점에서 다시 작성해보겠다.
예시로 나와있는 클린업 함수 내부에서 starClickedNum을 찾는 과정은 이렇다.
return에 있는 익명함수 → useEffect 내부 → 컴포넌트 전체 (useState에 의해 정의된 starClickedNum이 있음(찾음)) → !! 출력 !!
위의 모든 과정은 리랜더링 이전에 있던 렉시컬 환경에서 이뤄진다.
따라서 리랜더링되며 출력된 starClickedNum엔 새로운 렉시컬 환경의 업데이트 된 값이,
클린업 함수 내부에서 출력되는 값은 starClickedNum이 업데이트 되기 전 렉시컬 환경의 값이 들어가 있는 것이다..
클린업 함수도 결국엔 함수니까, 숨김 프로퍼티 [[Environment]] 가 있고, 이를 사용해서 자신이 만들어진 환경으로 간다. 그리고 변수에 접근한다.
+
new Date().toLocaleString()은 변수가 아니니까 굳이 렉시컬 환경에서 어떤 값이었는지 알 필요가 없다. 그냥 함수를 실행시켜서 클린업 함수 실행 시점의 시간을 출력하기만 하면 된다.
가렵던 부분을 긁은 느낌이다.
참고
https://ko.javascript.info/closure
변수의 유효범위와 클로저
ko.javascript.info
'프론트엔드' 카테고리의 다른 글
커링(Currying) (0) | 2023.08.22 |
---|---|
복사 벤치마크 및 비교 : JSON.stringify vs Lodash vs Object.assign vs 직접 구현 (0) | 2023.08.04 |
intersection observer의 entries 진짜 마지막으로 이해하기 (0) | 2023.07.31 |
무한 스크롤 Intersection observer로 구현하기 (0) | 2023.07.30 |
intersection observer 이해하기 (0) | 2023.07.30 |