import type { AnyFunction } from "../types";
import { useCallback } from "./useCallback";
import { useRef } from "./useRef";
export const useAutoCallback = <T extends AnyFunction>(fn: T): T => {
// 매 렌더링마다 latestFnRef.current 값이 업데이트됩니다.
const latestFnRef = useRef(fn);
latestFnRef.current = fn;
// dispatcherRef: "latestFnRef을 여는 행위"를 하는 함수를 담을겁니다. 해당 함수는 자체는 변하지 않음.
const dispatcherRef = useRef<(...args: any[]) => any>();
// "latestFnRef을 여는 행위"를 정의합니다.
// 이 함수는 매 렌더링마다 새로 생성됩니다.
// 따라서 항상 최신 스코프의 `latestFnRef`를 참조합니다.
const dispatcher = (...args: any[]) => {
return latestFnRef.current(...args);
};
// 새로 생성된 dispatcher 함수를 dispatcherRef의 내용물로 업데이트.
dispatcherRef.current = dispatcher;
// autoCallback: 이 함수의 참조는 절대 변하면 안 됩니다.
// autoCallback은 오직 "dispatcherRef를 열어서 그 안의 dispatcher를 실행"하는 것입니다.
const autoCallback = useCallback((...args: any[]) => {
// 이 함수가 호출될 때, 그 시점의 dispatcherRef를 열고,
// 그 안에 있는 최신 dispatcher를 실행합니다.
return dispatcherRef.current?.(...args);
}, []); // 참조 고정
return autoCallback as T;
};

const cb = useAutoCallback(...) 으로 만든 cb 변수는, 컴포넌트가 리렌더링되어도 항상 동일한 함수여야 한다.두 목표를 동시에 달성할 수 없음.
// 매 렌더링마다 latestFnRef.current 값이 업데이트됩니다.
const latestFnRef = useRef(fn);
latestFnRef.current = fn;
// dispatcherRef: "latestFnRef을 여는 행위"를 하는 함수를 담을겁니다. 해당 함수는 자체는 변하지 않음.
const dispatcherRef = useRef<(...args: any[]) => any>();
// "latestFnRef을 여는 행위"를 정의합니다.
// 이 함수는 매 렌더링마다 새로 생성됩니다.
// 따라서 항상 최신 스코프의 `latestFnRef`를 참조합니다.
const dispatcher = (...args: any[]) => {
return latestFnRef.current(...args);
};
// 새로 생성된 dispatcher 함수를 dispatcherRef의 내용물로 업데이트.
dispatcherRef.current = dispatcher;
// autoCallback: 이 함수의 참조는 절대 변하면 안 됩니다.
// autoCallback은 오직 "dispatcherRef를 열어서 그 안의 dispatcher를 실행"하는 것입니다.
const autoCallback = useCallback((...args: any[]) => {
// 이 함수가 호출될 때, 그 시점의 dispatcherRef를 열고,
// 그 안에 있는 최신 dispatcher를 실행합니다.
return dispatcherRef.current?.(...args);
}, []); // 참조 고정
return autoCallback as T;
autoCallback 호출:ref 상자의 내용물은 매 렌더링마다 최신 함수로 교체되므로,
autoCallback은 언제 호출되든 항상 최신 로직을 실행할 수 있었던 것입니다.
dispatcher 함수를 useRef 초기값이 아닌,
매 렌더링마다 생성하고 ref.current에 직접 할당한다는 점입니다.
➡️ useEffect 없이 클로저 문제를 우회(오래된 클로저)
❓ 오래된 클로저(Stale Closure)란?
클로저(함수의 "기억의 가방")가 과거 시점의 변수 값을 계속 기억하고 있어서,
이미 변경된 최신 값을 참조하지 못하는 현상
- 왜 "오래된(Stale)" 인가?
count state는 이미 1, 2, 3... 으로 바뀌며 "신선한" 상태가 되었습니다.
하지만 logCount의 클로저는 여전히 count가 0이었던 "오래된" 과거의 상태에 머물러 있습니다.- 왜 이런 일이 발생하는가?
- useCallback이나 useEffect 같은 훅에서 의존성 배열([])을 비워두면,
React는 성능 최적화를 위해 해당 함수나 효과를 최초 렌더링 시 단 한 번만 생성하고 재사용합니다.- 이때 함수가 생성되면서 최초 렌더링 시점의 state와 props를 클로저로 포획하게 되고,
그 이후로는 업데이트된 값을 알 방법이 없어지는 것입니다.