부모 컴포넌트가 자식 컴포넌트의 DOM 요소에 직접 접근할 수 있도록 도와주는 기능이다.
props
와 ref
로 이 함수를 호출한다.const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
Props
이다.ref
어트리뷰트이다. ref
는 객체나 함수일 수 있다.useImperativeHandle
에 전달해야 한다.💡
useImperativeHandle
란?
부모 컴포넌트가 자식 컴포넌트의 특정 기능이 상태에 접근할 수 있게 해주는 훅이다.
이는 리액트의 단방향 데이터 흐름과는 다르므로 신중하게 사용해야 한다.
JSX에서 렌더링할 수 있는 리액트 컴포넌트를 반환한다. 일반 함수로 정의된 리액트 컴포넌트와는 다르게, forwardRef
가 반환하는 컴포넌트는 ref prop
도 받을 수 있다.
함수형 컴포넌트에서는 클래스 컴포넌트와 달리, 인스턴스를 생성하지 않는다.
인스턴스가 없다는 것은this
를 통해 접근할 수 있는 컴포넌트 자체의 참조가 없다는 의미이다.
ref
는 일반적으로 컴포넌트의 인스턴스(상태, 생명주기 메서드 등)나 DOM 요소를 참조하는 데 사용되므로, 인스턴스가 없는 함수 컴포넌트에서는 ref
를 직접적으로 사용하기 어렵다.
ref
를 직접적으로 사용할 수 없기에props
로 전달해야 하는데, 리액트는 다음과 같은 이유들로ref
를props
에 포함하지 않았다.
특별한 처리
ref
는 일반적인 props
와 다르게 리액트에 의해 특수하게 처리되어야 한다.
ex) ref
는 컴포넌트가 마운트된 후에 설정되고, 언마운트되기 전에 null
로 재설정 되어야 한다.
일관성 유지
클래스 컴포넌트에서 ref
는 this.refs
를 통해 접근되며, props
의 일부가 아니다.
함수형 컴포넌트에서도 이와 유사한 동작을 유지하고자 했다.
의도적인 사용 제한
ref
의 직접적인 사용을 제한함으로써, 개발자들이 ref
를 신중하게 사용하도록 유도했다.
이는 선언적 프로그래밍 패러다임을 따르는 리액트의 철학과 일치한다.
리액트는 이 밖에 여러 이유들로 ref
를 props
에 포함시키지 않았다.
function Component(props) {
return <div>
Props: {JSON.stringify(props)}
</div>
}
export default function App() {
return <Component a={1} ref={() => {}}/>
}
자식 컴포넌트에 ref
를 전달하고 자식 컴포넌트에서 Props
를 직렬화 해보면 아래와 같은 결과를 얻을 수 있다.
➔ Props: {"a":1}
- 직렬화한 Props
에 ref
는 존재하지 않는 것을 확인할 수 있다.
컴파일한 리액트 앨리먼트를 확인 해보면 다음과 같이 Props
와 ref
가 따로 처리된다.
{
$$typeof: Symbol("react.element"),
key: null,
props: {a: 1},
ref: () => {},
type: function Component(props) {...},
_owner: ...,
_store: ...,
}
REACT_ELEMENT_TYPE
는 사용자 정의 함수/ 클래스 요소와 내장 HTML 태그를 다룬다.
export const REACT_ELEMENT_TYPE: symbol = Symbol.for('react.element');
export const REACT_FORWARD_REF_TYPE: symbol = Symbol.for('react.forward_ref');
export const REACT_SUSPENSE_TYPE: symbol = Symbol.for('react.suspense');
// 이외에도 REACT_PORTAL_TYPE, REACT_FRAGMENT_TYPE 등 다양한 타입들이 존재한다.
REACT_FORWARD_REF_TYPE
과 같은 타입들은 특별한 로직을 가진 특수한 타입이다.
이러한 타입들은 리액트가 내부적으로 컴포넌트의 종류를 식별하고 적절히 처리하는 데 사용된다.
export function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node
) {
const elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render, // 컴포넌트의 렌더링 함수
};
return elementType;
}
forwardRef()
는 함수형 컴포넌트 자체를 반환하지 않지만, 나중에 특수한 Fiber 노드 타입으로 매핑 될 특수한 리액트 앨리먼트 타입을 반환한다.
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const HostComponent = 5;
export const ForwardRef = 11;
export const LazyComponent = 16;
// 이외에도 HostPortal, HostRoot 등 다양한 태그들이 존재한다.
Fiber 영역에는 위와 같은 Fiber 노드 타입(Tag)들이 있다.
리액트 앨리먼트 타입(JSX에서 사용하는 타입)과 Fiber 타입은 완전히 1:1로 대응되지 않는다.
즉, 모든 리액트 앨리먼트 타입이 그대로 Fiber 타입으로 변환되지 않는다는 것이다.
(일부 Fiber 타입은 리액트 앨리먼트에 존재하지 않는 Fiber 시스템 전용 타입이다)
리액트 앨리먼트 타입과 Fiber 노드 타입이 다르기 때문에 매핑하는 과정이 필요하다.
아래는 리액트 앨리먼트 타입을 Fiber 노드로 매핑하는 코드이다.
export function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
lanes: Lanes
): Fiber {
let owner = null;
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes
);
return fiber;
}
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
lanes: Lanes
): Fiber {
let fiberTag = IndeterminateComponent;
let resolvedType = type;
if (typeof type === "function") {
if (shouldConstruct(type)) { // 클래스 컴포넌트
fiberTag = ClassComponent;
}
} else if (typeof type === "string") { // HTML 태그
fiberTag = HostComponent;
} else {
getTag: switch (type) {
default: {
if (typeof type === "object" && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
fiberTag = ContextConsumer;
break getTag;
case REACT_FORWARD_REF_TYPE: // forwardRef 매핑
fiberTag = ForwardRef;
break getTag;
// another code...
}
}
}
}
}
const fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.lanes = lanes;
return fiber;
}
다시 처음부터 흐름을 살펴보면 forwardRef()
함수를 통해 리액트 앨리먼트 타입(REACT_FORWARD_REF_TYPE
)을 설정하였고, 이렇게 설정한 앨리먼트 타입 기반으로 Fiber 노드 타입(ForwardRef
)으로 매핑하여 Fiber 노드를 생성한 것이다.
function beginWork(
current: Fiber | null, // 현재 화면에 렌더링된 Fiber 노드
workInProgress: Fiber, // 작업 중인 새로운 Fiber 노드
renderLanes: Lanes // 렌더링 우선 순위
): Fiber | null {
switch (workInProgress.tag) {
case ForwardRef: {
const type = workInProgress.type; // 컴포넌트 타입
const unresolvedProps = workInProgress.pendingProps; // 아직 처리되지 않은 Props
// elementType과 type이 같다면 unresolvedProps를 그대로 사용(컴포넌트의 변경이 없다)
// 다르면 resolveDefaultProps 함수를 통해 defaultProps 해결
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps : resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderLanes
);
}
}
}
beginWork
함수는 초기 렌더링과 리렌더링에 발생하며, Fiber 노드의 타입(workInProgress.tag
)에 따라 적절한 업데이트 로직을 선택한다.
Fiber 노드 타입이 ForwardRef
이기 때문에 updateForwardRef
함수를 호출한다.
function updateForwardRef(
current: Fiber | null,
workInProgress: Fiber,
Component: any, // forwardRef 컴포넌트
nextProps: any, // 새로운 props
renderLanes: Lanes
) {
const render = Component.render; // 컴포넌트 렌더링 함수
const ref = workInProgress.ref; // 현재 Fiber 노드의 ref
// 일반 컴포넌트 함수에서도 동일하게 처리되는 항목
let nextChildren;
let hasId;
prepareToReadContext(workInProgress, renderLanes);
// 실제로 컴포넌트가 실행되는 곳(render 함수 실행)
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref, // secondArg
renderLanes
);
hasId = checkDidRenderIdHook();
if (current !== null && !didReceiveUpdate) { // bailout(최적화) 관련 로직
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
if (getIsHydrating() && hasId) { // 하이드레이션 관련 로직
pushMaterializedTreeId(workInProgress);
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any, // render 함수
props: Props,
secondArg: SecondArg, // 두 번째 인자, ref
nextRenderLanes: Lanes,
): any {
// props와 ref가 전달되어 컴포넌트가 렌더링된다.
let children = Component(props, secondArg);
return children;
}
renderWithHooks
함수에서 두 번째 인자(secondArg
)는 레거시 항목으로 일반적인 함수 컴포넌트의 경우에서는 사용되지 않는다.
renderWithHooks
함수의 두 번째 인자라는 말이 이해가 안갈 수 있는데, renderWithHooks
함수에서 ref
를 전달하고 있는 다섯 번째 인자의 변수 이름이 secondArg
이고, forwardRef
함수의 두 번째 인자로 ref
를 전달하기 때문에 두 번째 인자라고 표현하고 있다.
function updateFunctionComponent(
current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes
) {
let context;
if (!disableLegacyContext && !disableLegacyContextForFunctionComponents) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
context = getMaskedContext(workInProgress, unmaskedContext);
}
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context, // 레거시 컨텍스트, secondArg
renderLanes
);
}
다섯 번째 인자로 context
를 넘겨주고 있는데, 이 컨텍스트는 더 이상 사용되지 않는 레거시 컨텍스트이다.
💡 여기서 레거시 컨텍스트는 리액트 초기 버전에서 사용되던 컨텍스트 시스템을 말한다.
현재는createContext()
를 사용하는 새로운 컨텍스트 API로 대체되었다.
아직도 존재하는 이유는 하위 호환성을 위해 유지되고 있다고 한다.
일반적인 함수 컴포넌트와 다르게 처리하기 위해 forwardRef
함수를 통해 리액트 앨리먼트 타입을 REACT_FORWARD_REF_TYPE
으로 설정한다.
설정한 리액트 앨리먼트 타입을 기반으로 Fiber 노드 타입 ForwardRef
을 매핑한다.
beginWork
함수에서 Fiber 노드 타입이 ForwardRef
일 경우, updateForwardRef
함수가 호출된다.
updateForwardRef
함수 내에서 renderWithHooks
함수를 사용하여 forwardRef
컴포넌트를 렌더링 한다. 이 때 두 번째 인자로 ref
를 전달한다.
forwardRef
컴포넌트의 render 함수는 props
와 ref
두 개의 인자를 받아 실행하여 개발자는 ref
를 원하는 내부 요소에 전달할 수 있다.
💡 요약하기
- 리액트 엘리먼트에서
ref
는props
와 별개로 처리된다.- 일반 함수 컴포넌트는 두 번째 인자(
ref
)를 사용하지 않는다.forwardRef
로 특별한 Fiber 노드를 생성하여ref
를 두 번째 인자로 전달한다.
리액트 19부터는 함수형 컴포넌트의 prop
으로 ref
에 접근할 수 있고, 향후 버전에서는 forwardRef
를 더 이상 사용하지 않고 제거 할 예정이라고 한다.
💡 클래스형 컴포넌트에 전달된
ref
는 컴포넌트 인스턴스를 참조하기 때문에 컴포넌트의 메서드를 직접 호출하거나 내부 상태에 접근할 수 있어props
로 전달되지 않는다.
How forwardRef() works internally in React?
리액트 공식 문서 - forwardRef
리액트 공식 문서 - useImperativeHandle
Experience the best of both worlds with Tez888, India’s premier destination for thrilling online experiences. From cricket and football to dynamic games and interactive challenges, Tez888 provides a secure, exhilarating, and immersive environment. Play smart, make confident choices, and elevate your experience with our diverse range of gaming options! Discover more at: https://tez888in.in
At 11exch, immerse yourself in a world of exciting online gaming, where sports, card games, and roulette await. Effortlessly manage your account, track your games, and access exclusive features. With a simple sign-up process, 11Exch App ensures a personalized and secure gaming experience tailored just for you. Get 11 exchange app now! Know more: https://11exch.ind.in/
I do believe this is an excellent website. I stumbled upon it I may revisit once again since I saved as a favorite it. https://www.smart-squarehmh.com
This article is so deep but i have one thing that can make you headache this game Five Nights at Freddy's, it is so deep that it can make you scared. try it!