useRef의 가장 중요한 임무는
입니다.
함수 컴포넌트를 "매일 출근하는 직원"이라고 상상해 보세요.
여기서 useRef가 하는 일이 바로 이 "개인 사물함"을 만들어주는 것입니다.
import { useState } from "react";
export function useRef<T>(initialValue: T): { current: T } {
const [ref] = useState(() => ({ current: initialValue }));
return ref;
}
첫 렌더링 (첫 출근 날)
컴포넌트가 처음 실행되면 useState({ current: initialValue }) 코드가 호출됩니다.
useState의 저장 공간)을 만들어 줘야겠다." 라고 생각합니다.{ current: initialValue } 라는 객체(사물함 내용물)를 만듭니다.refObject(객체)를 반환합니다.리렌더링 (다음 출근 날)
컴포넌트의 상태가 바뀌어서 리렌더링이 발생하면, useRef 함수 안의 코드는 다시 실행됩니다.
useState({ current: initialValue }) 코드가 또 호출됩니다. useState는 보통 [값, 값을 바꾸는 함수] 형태로 상태를 바꿀 때 쓰지만, 여기서는 그 기능을 쓰는 게 아닙니다.
useState의 "초기값은 맨 처음 딱 한 번만 사용되고, 그 이후에는 무시된다" 는 숨겨진 특징을 이용해서, 리렌더링 되어도 절대 변하지않는 "고정된 값(여기서는 객체)"을 만들어내는 "저장소" 역할로 활용한 것입니다.
이것이 바로 useRef가 useState로 구현될 수 있는 이유입니다.
React는 컴포넌트와 훅의 호출 순서를 이용해 기억합니다.
React가 이 모든 것을 기억할 수 있는 이유는, 훅이 호출되는 순서가 항상 같을 것이라고 믿기 때문입니다.
React 내부에는 각 컴포넌트마다 훅의 데이터를 저장하는 순서가 있는 리스트(배열)가 있습니다.
e.g) * MyComponent의 메모리: [ 훅1_데이터, 훅2_데이터, 훅3_데이터, ... ]
리렌더링될 때, React는 컴포넌트 코드를 다시 실행하면서 훅을 순서대로 만납니다.
"훅은 조건문이나 반복문 안에서 호출하면 안 된다"는 규칙이 있는 이유이며,
1 if (someCondition) {
2 useState(0); // 어떨 때는 호출되고, 어떨 때는 안 됨
3 }
4 useRef("cup");
React는 순서가 엉키면 어떤 데이터를 어디서 가져와야 할지 모르기 때문에 에러를 발생시키는 것 입니다.
📍 React는 컴포넌트별로 숨겨진 메모리 공간을 가지고 있고, 그 공간에 훅이 호출되는 순서대로 상태 값을 저장합니다.
리렌더링 시에는 그 순서에 맞춰 저장된 값을 다시 꺼내주는 방식으로 상태를 "기억"합니다.
import { useState } from "react";
export function useRef<T>(initialValue: T): { current: T } {
/* 최초 랜더링 시에 딱 한 번만 무언가를 만들고 그 참조를 계속 유지 */
// 지연 초기화
const [value] = useState(() => createExpensiveObject({ current: initialValue }));
return value;
}
/**
* 제네릭 타입 명시
* 1. createExpensiveObject 함수의 타입 지정
* 2. useRef<T> 와 useState 내부 값 타입이 정확히 매칭
* */
function createExpensiveObject<T>(value: { current: T }): { current: T } {
return value;
}
import { useState } from "react";
export function useRef<T>(initialValue: T): { current: T } {
const [ref] = useState(() => ({ current: initialValue }));
return ref;
}
정돌쓰 퀄리티 무슨 일,, 내용 재밌고 깔끔함 👍