setInterval은 보통 컴포넌트 첫 마운트 시에만 실행시킨다.
그렇기에 useEffect의 콜백함수 내부에서 실행시키게 되는데,
useEffect의 콜백함수는 컴포넌트 첫 실행 때 존재하던 상태 값만을 접근할 수 있게된다.
useEffect와 setInterval은 실행이 종료되었기 때문에 클로저가된 상태 값(setInterval 콜백이 선언된 시점의 상태)에만 접근할 수 있기 때문인 것 같다.
그렇기에 컴포넌트 라이프사이클에서의 변화되는 상태에 접근하지 못하고,
처음 캡쳐된 상태을 사용할 수 있기 때문에 실행될 때마다 초기화되는 것이다.
이 문제를 해결하기 위해서는 컴포넌트 라이프 사이클 내에 살아있는 상태에 접근해야한다. 2가지 방법이 있다.
첫번째 방법
변경하려는 상태에 대해서는 최신값에 접근할 수 있지만,
나머지 상태나 props등의 환경 값들은 여전히 선언 시의 값 그대로인 문제점이 있다.
2번째 방법
- 브라우저에 등록될 setInterval()의 콜백함수는 ref.current를 참조해, 그곳에 저장된 함수를 실행시키는 로직이 담겨있다.
- ref.current는 가변변수이기 때문에 값이 바뀔 수 있다.
- useInterval()은 ref.current에 저장할 콜백함수를 인자로 받는데, 상태를 변화시키는 로직이 담겨있다.
- useInterval()은 컴포넌트가 리렌더링될 때마다 실행된다.
- 즉 리렌더링 될 때마다 최신 상태가 적용된 콜백함수가 useInterval()의 인자를 입력받아 실행되게 되고,
- useInterval() 내부에서, 입력된 콜백함수를 ref.current에 할당하는 로직으로 인해 ref.current값이 갱신되게 된다.
- 그렇기에 매번 최신 상태와 그 환경이 저장된 함수를 실행시킬 수 있다.
돔이 생기고 없어지는 거랑 컴포넌트가 실행되는 거랑은 다르다!!
컴포넌트가 첫 렌더링되어 돔이 생기고 화면에서 없어질 때까지가 컴포넌트의 라이프사이클이고,
재실행되어 리렌더링이 되는 건 변화된 부분만 만들어진 돔에 반영하는 것
상태는 컴포넌트가 실행될 때마다 새로 생기는 것이 아닌, 컴포넌트의 라이프 사이클 주기 내에서 계속 살아있음.
const, let처럼 일반적인 변수는 컴포넌트 실행 시마다 새로 생성+초기화됨.
ref 는 상태와 마찬가지로 컴포넌트 라이프사이클 주기 내에서 계속 살아있다.
상태와 ref의 차이점
상태는 변경될 때마다 컴포넌트를 재실행 시킴(바뀐 상태에 맞춰 화면을 변경하기 위해)
ref는 ref.current에 값을 저장해 사용하기 때문에 ref.current가 변경되어도 리액트는 변경을 감지하지 못하고 그렇기에 재실행되지도 않는다.
react.memo와 useCallback을 사용해야하는 이유
일반적인 변수는 함수 재실행 때마다 재생성되는데, 값이 변하지 않았다면 매번 새로 만들어지는게 비효율적이다. 그렇기에 값이 바뀌는 조건이 일어나지 않는다면 재생성을 막고, 처음 저장한 값을 기억해서 사용하는게 좋다.