공부한 내용을 정리한 글입니다
내용에 오류가 있거나 더 좋은 의견이 있다면 댓글로 남겨주세요.
배움에 큰 도움이 됩니다. 🖋
React의 선언형 프로그래밍과 UseImperativeHandle
React는 기본적으로 선언형 프로그래밍을 권장하는데, 이는 jsx를 활용해 ui를 표현하는 방식에서도 잘 드러난다.
export const Component = ({title, description}) => (
<div>
<h1>{title}</h1>
<p>{description}</p>
</div>
);
선언형 프로그래밍은 '무엇을' 하는지에 집중하며, 추상화를 통해 구현의 세부사항을 숨길수가 있다.
아래의 함수형 프로그래밍은 선언형의 한 예시다
const sum = (a, b) => a + b; // "무엇을" 하는지 명시
반면 명령형 프로그래밍은 '어떻게' 하는지에 중점을 두며, 세부 단계를 명확히 알 수 있다.
let sum = 0;
for (let i = 1; i <= 10; i++) {
sum += i; // "어떻게" 하는지 명시
}
UseImperativeHandle은 이름에서도 느낄 수 있듯 react에서 명령형 프로그래밍을 가능하게 하는 hook이다. 생성된 ref를 통해 부모 컴포넌트에서 자식 컴포넌트의 메서드에 직접적으로 접근할 때 사용한다.
명령형으로 작성된 외부 라이브러리의 api를 사용하거나 포커싱 및 애니메이션을 제어할 때 유용하겠다.
어떻게 사용할까
아래는 비디오 플레이어를 생성하고, 외부 라이브러리의 메소드를 명령형 스타일로 부모 컴포넌트에 노출하는 코드다.
외부 라이브러리의 api 이용
import React, { forwardRef, useImperativeHandle, useEffect, useRef } from 'react';
import videojs from 'video.js';
const VideoPlayer = forwardRef((props, ref) => {
const videoRef = useRef();
useEffect(() => {
// video.js를 사용하여 비디오 플레이어 생성
const player = videojs(videoRef.current, props);
// 외부 라이브러리와 통합하여 명령형 스타일로 메소드 노출
useImperativeHandle(ref, () => ({
play: () => {
player.play();
},
pause: () => {
player.pause();
},
...
}));
return () => {
if (player) {
player.dispose();
}
};
}, [props, ref]);
return <div data-vjs-player><video ref={videoRef} className="video-js" /></div>;
});
// 부모 컴포넌트에서 Ref 생성
const ParentComponent = () => {
const videoRef = useRef();
const handlePlayButtonClick = () => {
// 외부 라이브러리 메소드 호출
videoRef.current.play();
};
...
return (
<div>
<VideoPlayer ref={videoRef} />
<button onClick={handlePlayButtonClick}>Play</button>
...
</div>
);
};
export default ParentComponent;
위 코드처럼 주로 forwardRef와 함께 사용되며, 부모 컴포넌트에서는 노출된 메서드를 호출해 자식 컴포넌트의 내부 상태에 접근할 수 있게 된다.
포커싱
// 자식 컴포넌트
const ChildComponent = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
});
// 부모 컴포넌트
const ParentComponent = () => {
const childRef = useRef();
const handleFocus = () => {
childRef.current.focus();
};
return (
<>
<ChildComponent ref={childRef} />
<button onClick={handleFocus}>자식 컴포넌트에 포커스</button>
</>
);
물론 useImperativeHandle 없이 forwardRef만 사용해도 같은 작업을 수행할 수 있지만, useImperativeHandle를 사용하면 자식 컴포넌트를 좀 더 추상화해 부모 컴포넌트와의 의존성을 줄일수가 있다.
// 자식 컴포넌트
const ChildComponent = forwardRef((props, ref) => {
const focusInput = () => {
// 어떤 동작 수행
};
useImperativeHandle(ref, () => ({
focusInput,
}));
return <input />;
});
// 부모 컴포넌트
const ParentComponent = () => {
const childRef = useRef();
const handleFocus = () => {
childRef.current.focusInput();
};
return (
<>
<ChildComponent ref={childRef} />
<button onClick={handleFocus}>자식 컴포넌트에 포커스</button>
</>
);
정리하기
기본적으로는 react가 권장하는 선언형 프로그래밍 방식을 따르되, 명령형으로 작성된 외부 라이브러리의 api를 사용하거나 부모 컴포넌트에서 자식 컴포넌트의 dom을 포커싱하거나 직접 애니메이션을 제어할 일이 생길 때 useImperativeHandle을 사용해보자.
참고
https://react-ko.dev/reference/react/useImperativeHandle
댓글