본문 바로가기
카테고리 없음

[React] React의 선언형 프로그래밍과 UseImperativeHandle

by 2__50 2024. 1. 5.
공부한 내용을 정리한 글입니다 
내용에 오류가 있거나 더 좋은 의견이 있다면 댓글로 남겨주세요.
배움에 큰 도움이 됩니다. 🖋

 

 

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

 

useImperativeHandle – React

The library for web and native user interfaces

react-ko.dev

 

댓글