Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[렉시컬 스코프, 클로저 개념] setInterval 내 람다의 left 변수는 왜 그대로일까? (재현님 공유) #21

Open
DaehunGwak opened this issue Mar 15, 2024 · 5 comments

Comments

@DaehunGwak
Copy link
Owner

DaehunGwak commented Mar 15, 2024

문제의 코드.. useEffect > setInterval 람다 > left 변수가 왜 그대로인가?

요약: 결국 아래 코드의 문제는 setInterval 람다 생성 시 그때 당시 스코프 체인에 따라 그때 시점의 App 스코프 렉시컬 환경을 저장하고 있고, 그때 자유 변수 time=1500 을 스냅샷찍음 > setInterval 내부에서 람다 레퍼런스를 들고 있어 해당 렉시컬 환경이 사라지지 않고, 따라서 매번 호출할때마다 time 1500을 들고오게됨

import { useEffect, useState } from "react";

function App() {
    const [time, setTime] = useState(25 * 60 * 1);
    const [isStart, setIsStart] = useState(false);

    function computeTime(t: number) {
        return --t;
    }

    function onButtonPress() {
        setIsStart((prev) => !prev);
    }

    useEffect(() => {
        if (isStart) {
            setInterval(() => {
                const left = computeTime(time);
                console.log(`left time seconds: ${left}`);
                setTime(left);
            }, 1000);
        }
    }, [isStart]);

    return (
        <div
            style={{
                width: "100%",
                height: "100vh",
                backgroundColor: "teal",
                display: "flex",
                flexDirection: "column",
                justifyContent: "center",
                alignItems: "center",
            }}
        >
            <button onClick={onButtonPress}>START</button>
            <p
                style={{
                    fontWeight: "bold",
                    color: "white",
                }}
            >
                {time}
            </p>
        </div>
    );
}

export default App;
@jh0152park
Copy link

reproduction video

2024-03-16.12.30.27.mov

@DaehunGwak

This comment was marked as off-topic.

@jh0152park
Copy link

jh0152park commented Mar 15, 2024

아래 영상과 같이 useEffect의 dependencies array에 time을 추가하게 되면 state가 정상적으로 update 되는것 처럼 보이나
영상 후반부와 같이 비정상적으로 state가 동작하는걸 확인하실 수 있습니다😭

관련해서 위와같은 동작이 일어난 이유를 나름 생각해본 내용은 아래와 같습니다.

  1. button click시 useEffect가 state change를 listen 하고 있다가 detect한 후 setInterval 초기 1회 수행
  2. useEffect의 dependencies array에 time이 포함되어있어, state changed detect 후 setInterval 함수가 추가적으로 실행
  3. 2번 항목이 반복됨, 따라서 button click 이후 매초에 setInterval 함수가 지속적으로 call stack에 쌓이는게 아닌가 의심?
    즉 n초마다 n개의 setInterval이 중첩되어서 call된다?

정확한지는 모르겠지만! 일단 제가 유추하기로는 이렇습니다!

useEffect(() => {
        if (isStart) {
            setInterval(() => {
                const left = computeTime(time);
                setTime(left);
                console.log(`left time seconds: ${time}`);
            }, 1000);
        }
    }, [isStart, time]);

working video

2024-03-16.1.21.25.mov

@DaehunGwak
Copy link
Owner Author

DaehunGwak commented Mar 15, 2024

클로저, 함수를 생성한 시점에 소코프 체인에서 들고온 변수를 저장하고 있음

  • 상위 스코프
    • 함수 호출 되는 시점에 결정 - 동적 스코프
    • 함수 정의 되는 시점에 결정 - 정적(렉시컬) 스코프
  • 함수 생성시 상위 스코프에 대한 참조를 저장
  • 함수 호출 > 실행 컨텍스트 생성 > 렉시컬 환경 생성 (포함하는 식별자, 상위 스코프 저장)
  • 포인트는 함수 레퍼런스를 계속 가지고 있으면 호출 시점에 생성된 스코프를 스냅샷 떠서 계속 가지고 있는 것임.... (그때 스냅샷뜬 변수를 자유 변수, 그때 생성된 함수를 클로저라 표현함)
  • 결국 위의 문제는 setInterval 람다 생성 시 그때 당시 스코프 체인에 따라 그때 시점의 App 스코프 렉시컬 환경을 저장하고 있고, 그때 자유 변수 time=1500 을 스냅샷찍음 > setInterval 내부에서 람다 레퍼런스를 들고 있어 해당 렉시컬 환경이 사라지지 않고, 따라서 매번 호출할때마다 time 1500을 들고오게됨

image

references

https://ziszini.tistory.com/64
https://youtu.be/MbYShFxp-j0?si=zb0vBUJZy9i8cLXR
https://youtu.be/PVYjfrgZhtU?si=kUqMFFnJ5pbPXvXc

@DaehunGwak DaehunGwak changed the title 이건 왜 처음에 setInterval 내 left 가 한번만 초기화 될까? (재현님 공유) [렉시컬 스코프, 클로저 개념] 이건 왜 처음에 setInterval 내 left 가 한번만 초기화 될까? (재현님 공유) Mar 15, 2024
@DaehunGwak DaehunGwak changed the title [렉시컬 스코프, 클로저 개념] 이건 왜 처음에 setInterval 내 left 가 한번만 초기화 될까? (재현님 공유) [렉시컬 스코프, 클로저 개념] setInterval 내 람다의 left 변수는 왜 그대로일까? (재현님 공유) Mar 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants