리액트의 훅 중 하나로, 함수형 컴포넌트에서 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()
유익해서 블로그에 글 일부 내용을 첨부해도될까요?