리액트의 훅 중 하나로, 함수형 컴포넌트에서 DOM 요소나 값을 참조하기 위해 사용된다.
1. DOM 요소 접근: 특정 DOM 요소에 대한 참조를 생성할 수 있다. 이를 통핸 컴포넌트가 렌더링된 후 해당 요소에 직접 접근할 수 있다.
2. 값 저장: 일반적인 상태와 다르게 useRef()
로 저장된 값은 컴포넌트가 리렌더링 되어도 유지가 되고, 값이 업데이트되어도 리렌더링을 일으키지 않는다.
function mountRef<T>(initialValue: T): {current: T} {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
1. mountWorkInProgressHook()
으로 새로운 훅 생성
2. current
프로퍼티를 가진 ref
객체 생성, 초기 값은 initialValue
로 할당
3. hook.memoizedState
에 ref
를 할당하여, 해당 훅이 리렌더링될 때 이 상태를 유지하도록 한다. 이는 리액트의 내부 훅 상태 관리 방식이다.
function updateRef<T>(initialValue: T): {current: T} {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
1. updateWorkInProgressHook
현재 진행 중인 렌더링 과정에서 어떤 훅이 호출되고 있는지를 추적한다. 이를 통해 각 훅이 자신의 상태를 올바르게 업데이트하고 유지할 수 있다.
2. hook.memoizedState를 반환한다. 여기서 memoizedState
는 이전에 저장된 ref
객체이다.
// finishedWork는 현재 처리 중인 컴포넌트의 정보를 담고 있는 Fiber 객체이다.
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) { // ref가 null이 아닌 경우에만 실행
const instance = finishedWork.stateNode; // 컴포넌트의 인스턴스 가져오기
let instanceToUse;
// 태그에 따라 어떤 인스턴스를 사용할 지 결정
switch (finishedWork.tag) {
case HostHoistable:
case HostSingleton:
// HostComponent는 실제 DOM 요소를 나타내는 컴포넌트이다.
// <div>, <span> 같은 HTML 요소
case HostComponent:
// 공개 인스턴스 가져오기
instanceToUse = getPublicInstance(instance);
break;
default:
// 원래 인스턴스 사용
instanceToUse = instance;
}
if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
instanceToUse = instance;
}
if (typeof ref === 'function') {
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
finishedWork.refCleanup = ref(instanceToUse);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
finishedWork.refCleanup = ref(instanceToUse);
}
} else {
ref.current = instanceToUse;
}
}
}
commitAttachRef()
함수는 리액트의 내부에서 컴포넌트가 커밋될 때 참조를 설정하는 역할이다.
1. ref
가져오기
ref
를 가져온다. 만약 ref
가 null
이라면 다음 단계를 진행한다.2. 인스턴스 결정
stateNode
라는 속성에 저장되어 있다.3. DOM 노드 설정
HostComponents
라면, getPublicInstance
를 통해 DOM 노드를 가져온다. 이는 실제 DOM 요소에 대한 참조를 설정하는 과정이다.4. 콜백 ref
ref
가 함수일 경우, 함수를 호출하여 인스턴스를 설정한다. 이러한 경우에 리액트는 이 참조를 렌더링 후 DOM이 업데이트된 직후에 설정하며, 이는 useLayoutEffect
가 실행되기 전에 발생한다.즉 컴포넌트가 화면에 렌더링되기 전에 참조가 설정되므로, useLayoutEffect
와 useEffect
보다 더 빠르게 접근할 수 있다.
ref
의 예시 코드function InputComponent(){
// 콜백 ref 함수
const setRef = useCallback((element: HTMLInputElement) => {
if (element) {
element.focus(); // 인풋 포커스
}
}, []);
return (
<input
type="text"
ref={setRef} // 콜백 ref 사용
placeholder="인풋 포커스 테스트"
/>
);
}
ref
와 useEffect
비교콘솔 창을 확인해보면 콜백 ref 포커스 시점이 더 빠르고 useEffect
가 늦게 포커스 이벤트를 주어서 useEffect
쪽 인풋에 포커스가 되어있는 것을 볼 수 있다.
💡 기본 인스턴스(Default Instance)와 공개 인스턴스(Public Instance)의 차이
기본 인스턴스(Default Instance)
리액트 컴포넌트가 생성될 때 내부에서 사용되는 인스턴스를 말한다.
이 인스턴스는 리액트의 생명주기 메서드와 상태 관리 등을 포함하고 있다.
리액트의 내부 로직에서 사용되며, 일반적으로 사용자 코드에서는 직접 접근하지 않는다.
공개 인스턴스(Public Instance)
사용자가 직접 접근할 수 있는 인스턴스이다. 즉, 컴포넌트의 메서드나 속성에 접근할 수 있는 방법을 제공한다.
주로 DOM 요소에 대한 참조를 제공하는데, 이는 다른 라이브러리나 코드에서 해당 DOM 요소를 조작하거나 상태를 확인할 수 있게 해준다.
function commitDetachRef(current: Fiber) {
const currentRef = current.ref; // 현재 Fiber 노드의 ref를 가져온다.
if (currentRef !== null) {
if (typeof currentRef === "function") {
currentRef(null);
} else {
currentRef.current = null;
}
}
}
함수형 ref
처리: currentRef
가 함수인 경우, 리액트는 이 함수를 호출하여 null
을 전달한다.
객체형 ref
처리: currentRef
가 객체인 경우, 이 객체의 current
속성을 null
로 설정하여 참조를 제거한다.
이는 클래스형 컴포넌트나 useRef
훅을 사용할 때의 일반적인 처리 방식이다.
이 함수는 리액트의 렌더링 과정에서 컴포넌트가 언마운트되거나 업데이트될 때, 해당 컴포넌트의 참조를 안전하게 제거하는 역할을 한다.
💡
ref
가 언마운트 전에null
로 재설정되는 이유
ref
가 계속 DOM 요소를 참조하고 있으면, 해당 요소가 DOM에서 제거되어도 메모리에서 해제되지 않을 수 있다.- 컴포넌트가 언마운트된 후에도
ref
가 이전 DOM 요소를 가리키고 있다면, 이는 잘못된 상태를 나타낸다.
💡 위 연결, 분리 함수들은 호출되기 전에
flags
를 통해 필요 여부를 확인한다.
if (finishedWork.flags & Ref) { // 1. 플래그 검사
commitAttachRef(finishedWork); // 2. 참조 연결
}
if (flags & Ref) { // 3. 현재 작업의 플래그 검사
const current = finishedWork.alternate; // 4. 이전 상태 가져오기
if (current !== null) { //
commitDetachRef(current); // 5. 참조 분리
}
}
1. 플래그 검사
finishedWork
객체의 flags
속성에 Ref
플래그가 설정되어 있는지를 검사한다.Ref
플래그가 설정되어 있다면, 해당 컴포넌트에 참조가 있다는 것을 의미한다.2. 참조 연결
Ref
플래그가 설정되어 있다면, finishedWork
에 대한 참조를 추가한다.finishedWork
의 ref
를 적절히 설정하여 컴포넌트가 마운트될 때 참조를 연결한다.3. 현재 작업의 플래그 검사
Ref
가 있는지 검사한다. 현재 컴포넌트의 상태가 변경되었음을 나타낸다.4. 이전 상태 가져오기
finishedWork
의 이전 상태를 가져온다(이전 Fiber 노드를 참조). 이 노드는 주로 현재 상태와 비교하기 위해 사용된다. 5. 참조 분리
// current: 현재 Fiber 노드, 이전 상태를 나타낸다.
// workInProgress: 현재 작업 중인 Fiber 노드
function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref;
if (
(current === null && ref !== null) ||
(current !== null && current.ref !== ref)
) {
// Schedule a Ref effect
workInProgress.flags |= Ref;
if (enableSuspenseLayoutEffectSemantics) {
workInProgress.flags |= RefStatic;
}
}
}
markRef()
는 조정(reconciliation)단계 내부에 있는updateHostComponent()
에서 호출된다.
첫 번째 조건: current
가 null
이고 ref
가 null
이 아닐 때
↪ 이전 상태가 없고 현재 상태에 ref
가 새로 추가된 경우
두 번째 조건: current
가 null
이 아니고 이전 상태의 ref
와 현재 상태 ref
가 다를 때
↪ ref가 변경된 경우
이 두가지 조건 중 하나라도 참인 경우 workInProgress
의 flags
에 Ref
플래그를 설정한다.
이는 이후에 참조 효과를 예약하기 위해 필요한 작업이다.
여기서 참조 효과는 리액트에서 ref가 생성되거나 변경될 때 발생하는 작업을 의미한다.
useRef()
는 ref
객체만 보유하는 간단한 훅이다.
조정하는 동안, ref
의 생성 및 변경은 flags
의 Fiber에 표시(mark)된다. ➔ markRef()
커밋하는 동안, 리액트는 flags
를 확인하여 ref
를 연결 및 분리 작업을 실행한다.
➔ commitAttachRef()
, commitDetachRef()