반응형
해당 글에서는 React에서 forwardRef, useImperativeHandle를 이해하고 활용하는 방법에 대해 알아봅니다.
1) forwardRef
💡forwardRef
- React에서 제공하는 고차 컴포넌트(Higher-Order Component)로 부모 컴포넌트에서 자식 컴포넌트로 ref를 전달할 수 있게 해줍니다.
- 이는 주로 재사용 가능한 컴포넌트 라이브러리를 만들 때 유용하며, 특히 DOM 요소나 클래스 컴포넌트의 인스턴스에 직접 접근해야 할 때 사용됩니다.
[ 더 알아보기 ]
💡 forwardRef 선언하는 곳은 부모 컴포넌트인가? 아니면 자식 컴포넌트인가?
- 자식 컴포넌트에서 선언됩니다. 이는 자식 컴포넌트가 부모로부터 전달받은 ref를 사용할 수 있게 해주는 역할을 합니다.
- 자식 컴포넌트에서 forwardRef를 사용하여 컴포넌트를 감싸고, ref를 두 번째 매개변수로 받아 사용합니다.
- 부모 컴포넌트에서는 ref를 생성하고 이를 자식 컴포넌트에 prop으로 전달합니다.
1. forwardRef의 주요 특징
특징 | 설명 |
ref 전달 | 부모 컴포넌트에서 생성한 ref를 자식 컴포넌트로 전달할 수 있습니다. |
컴포넌트 래핑 | 기존 컴포넌트를 감싸 새로운 컴포넌트를 생성합니다. |
투명한 프로퍼티 전달 | ref 이외의 props는 자동으로 래핑된 컴포넌트로 전달됩니다. |
2. 기본 구조
💡 기본 구조
1. SomeComponent: forwardRef로 생성된 새로운 컴포넌트입니다.
2. forwardRef: React에서 제공하는 고차 컴포넌트(Higher-Order Component)입니다.
3. render: 컴포넌트의 렌더링 로직을 포함하는 함수들 입니다. 이 함수는 props와 ref를 인자로 받습니다
import { forwardRef } from "react";
import { forwardRef, useImperativeHandle } from "react";
// 기본 구조
const SomeComponent = forwardRef(render)
// 기본 구조 : 상세-1
const SomeComponent = forwardRef(props, ref);
// 기본 구조 : 상세-2
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
});
// 기본 구조 : 상세 -3
export const SomeComponent = forwardRef(({ props }, ref) => {
return (
<></>
)
})
2) useImperativeHandle
💡 useImperativeHandle
- React Hook으로 부모 컴포넌트에서 자식 컴포넌트의 내부 함수나 값에 접근할 수 있게 해주는 기능을 제공합니다.
- 이를 사용하면 자식 컴포넌트에서 부모 컴포넌트로 특정 기능을 노출시키는 기능을 사용할 수 있습니다.
1. 주요 특징
특징 | 설명 |
forwardRef와 함께 사용 | ref를 통해 부모-자식 간 통신을 가능하게 함 |
선택적 기능 노출 | 자식 컴포넌트의 내부 구현을 숨기면서 필요한 기능만 노출 가능 |
직접 상호작용 | 컴포넌트의 생명주기나 렌더링에 영향을 주지 않고 ref를 통해 직접 상호작용 가능 |
2. 기본 구조
💡기본 구조
1. ref: 부모 컴포넌트에서 전달받은 ref 객체입니다. 이를 통해 부모 컴포넌트가 자식 컴포넌트의 기능에 접근할 수 있게 됩니다.
2. createHandle: 함수로, 부모 컴포넌트에 노출시키고자 하는 메서드나 속성들을 포함하는 객체를 반환합니다. 이 객체를 통해 자식 컴포넌트의 내부 구현을 숨기면서 필요한 기능만 선택적으로 노출할 수 있습니다.
3. dependencies?: 선택적 파라미터로, 의존성 배열입니다. 이 배열 내의 값들이 변경될 때만 createHandle 함수가 재실행됩니다. 이를 통해 성능을 최적화할 수 있습니다.
useImperativeHandle(ref, createHandle, dependencies?)
3. 사용예시
💡 사용예시
- 부모 컴포넌트와 자식 컴포넌트가 있다는 가정하에, 자식 컴포넌트 내에서 선언한 내용입니다.
- 각각 부모 컴포넌트에서 활용가능한 메서드 start, stop, pause으로 정의하였고, useImperativeHandle을 통해서 자식 컴포넌트에서 구성한 메서드와 연결하였습니다.
/**
* 부모 컴포넌트로 전달할 값
*/
useImperativeHandle(ref, () => ({
start: stopwatchHandler.start, // 스탑워치를 시작합니다.
pause: stopwatchHandler.pause, // 스탑워치를 멈춥니다.
stop: stopwatchHandler.stop, // 스탑워치를 종료합니다.
}));
const stopwatchHandler = (() => {
return {
start: () => console.log("[+] 스탑워치를 시작합니다."),
stop: () => console.log("[+] 스탑워치를 종료합니다."),
pause: () => console.log("[+] 스탑워치를 일시정지합니다."),
}
})()
반응형
3) forwardRef, useImperativeHandle를 활용한 예시 : 부모/자식 관계
💡 forwardRef, useImperativeHandle를 활용한 예시 : 부모/자식 관계
- forwardRef와 useImperativeHandle을 사용하여 부모 컴포넌트와 자식 컴포넌트 간의 통신을 구현한 예시입니다
1. 자식 컴포넌트
💡 자식 컴포넌트
- forwardRef를 사용하여 부모로부터 전달받은 ref를 처리합니다.
- stopwatchHandler 객체를 통해 실제 스톱워치 기능을 구현합니다.
- useImperativeHandle을 사용하여 부모 컴포넌트에서 접근 가능한 메서드(start, pause, stop)를 정의합니다.
import { forwardRef, useImperativeHandle } from 'react';
export const ChildComponent = forwardRef(({ parentProps1, parentProps2 }: { parentProps1: string; parentProps2: string }, ref) => {
const stopwatchHandler = (() => {
return {
start: () => console.log('[+] 스탑워치를 시작합니다.'),
stop: () => console.log('[+] 스탑워치를 종료합니다.'),
pause: () => console.log('[+] 스탑워치를 일시정지합니다.'),
};
})();
/**
* 부모 컴포넌트로 전달할 값
*/
useImperativeHandle(ref, () => ({
start: stopwatchHandler.start, // 스탑워치를 시작합니다.
pause: stopwatchHandler.pause, // 스탑워치를 멈춥니다.
stop: stopwatchHandler.stop, // 스탑워치를 종료합니다.
}));
return <div>자식 컴포넌트를 출력합니다.</div>;
},
);
2. 부모 컴포넌트
💡 부모 컴포넌트
1. componentRef
- useRef를 사용하여 자식 컴포넌트에 대한 참조(componentRef)를 생성합니다.
- ChildFuncType을 통해 전달 받을 함수를 지정하였습니다.
2. childCallHandler
- 자식 컴포넌트의 메서드(start, stop, pause)를 호출할 수 있는 함수들을 정의합니다.
3. button
- 컴포넌트를 사용하여 사용자가 '시작', '중지', '종료' 버튼을 누르면 자식 컴포넌트의 함수를 호출 할 수 있도록 합니다.
import { useRef } from 'react';
import { ChildComponent } from './ChildComponent';
/**
* 자식 컴포넌트에서 전달해오는 useImperativeHandle 값
*/
type ChildFuncType = {
start: () => void;
stop: () => void;
pause: () => void;
};
const ParentComponent = () => {
const componentRef = useRef<ChildFuncType>(null);
/**
* 자식 컴포넌트의 함수를 호출
*/
const childCallHandler = (() => {
return {
start: () => {
if (componentRef.current) {
componentRef.current.start();
}
},
stop: () => {
if (componentRef.current) {
componentRef.current.stop();
}
},
pause: () => {
if (componentRef.current) {
componentRef.current.pause();
}
},
};
})();
return (
<div>
<div style={{ marginBottom: 10 }}> 부모 컴포넌트 입니다.</div>
<div style={{ marginBottom: 10 }}>
<button onClick={childCallHandler.start}>시작</button>
<button onClick={childCallHandler.pause}>중지</button>
<button onClick={childCallHandler.stop}>종료</button>
</div>
<ChildComponent ref={componentRef} parentProps1='props' parentProps2='props2' />
</div>
);
};
export default ParentComponent;
4) 결과 확인
💡 결과 확인
- 부모 컴포넌트 화면 내에서 자식 컴포넌트 화면이 출력되었고 부모 컴포넌트의 버튼을 눌렀을때, 자식 컴포넌트의 함수가 호출이 되었습니다.
오늘도 감사합니다 😀
반응형