카테고리 없음

React의 ref에 대해 알아보자

썽연 2025. 2. 12. 17:07
728x90

Ref란 무엇일까?

  • Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 하나의 방식이다.

Ref는 그럼 언제 사용할까?

  • 포커스, 텍스트 선택 영역, 혹은 미디어의 재생 관리
  • 애니메이션 직접적으로 실행시킬 때
  • 서드 파티 DOM 라이브러리를 React 와 같이 사용할 때

라고 공식문서에 나와 있다.

하지만 나는 이것을 보고 정확히 무엇인지? 이해가 되지 않았다.

여기서 나는 React의 렌더링 조건이 생각난다.

  1. props가 변할 때
  2. state가 변할 때
  3. 부모 컴포넌트가 리렌더링 될 때

React는 state가 바뀌면 렌더링이 되는 반면, ref는 DOM에 직접 접근하기 때문에 렌더링이 되지 않는다!

그래서 나는 Ref를 보통, 불 필요한 렌더링을 막기 위해서 사용하곤 한다.

  1. DOM에 직접 접근
  2. 상태 변경 값 없이 값 가져오기
  3. 애니메이션

이 주로 이용될 수 있다고 생각한다.

forwardRef에 대해 알아보자

React에서는 ref를 사용하여 DOM 요소 또는 컴포넌트 인스턴스에 직접 접근이 가능한데, 부모 컴포넌트에서 자식 컴포넌트에게 state를 전달하여 props를 받는 반면 ref를 전달하고 싶을 땐 바로 forwardRef를 이용한다 !

즉 부모 컴포넌트에서 생성된 ref를 자식 컴포넌트에게 전달하며, 자식 컴포넌트는 부모 컴포넌트와 자식 컴포넌트의 인스턴스에 접근할 수 있다!

forwardRef는 자식 컴포넌트 내부 구조와 동작을 부모 컴포넌트에 노출시켜, 컴포넌트 간의 강한 결합을 초래할 수 있다.

자식 컴포넌트가 변경되면 부모 컴포넌트도 함께 변경될 수 있어

useImperativeHandle 훅을 react에서 제공한다

useImperativeHandle가 뭔데?

부모 컴포넌트로 노출되는 인스턴스 값을 직접 커스터마이징할 수 있게 해준다.

이를 사용하면 자식 컴포넌트는 필요한 메소드와 값을 선택적으로 부모 컴포넌트에 노출시킬 수 있다.

자식 컴포넌트는 필요한 메소드와 값을 선택적으로 부모 컴포넌트에 노출이란 무슨 뜻일까?

나는 디자인 시스템을 개발할때 input같은 경우 불필요한 렌더링 방지를 위해 ref를 이용하여 값을 이용하곤 했다.

const Input = forwardRef<HTMLInputElement, Props>(({
	label, disabled, type ...}, ref) => {
		
		**useImperativeHandle(ref, () => inputRef.current);**
		
		return (
			<Container>
				<StyledInput type={type} disabled={disabled} label={label} ref={ref}/>
			</Container>
		);
});

위와 같이 예시 코드가 있다.

해당 코드는 Input 컴포넌트를 사용하는 부모 컴포넌트가 ref를 통해 inputRef.current를 직접 접근할 수 있도록 하는 것이다.

Input는 forwardRef를 통해 ref를 전달받아, 내부적으로 useRef를 사용해 inputRef를 생성하고, <input> 에 연결한다.

 

useInperativeHandle을 사용하여 부모 컴포넌트가 ref를 통해 inputRef.current를 직접 접근할 수 있도록 허용해주는 것이다.

 

만약 여기서 useImperativeHandle(ref, () ⇒ inputRef.current); 가 없다면, 예를들어 current 값이 undefined가 되어 focus, value 등 직접 호출할 수 없다.

즉, useImperativeHandle을 사용하지 않으면 ref가 Container에 바인딩되어 input에 조작할 수 없다!

그렇다면 여기서 궁금한건, forwardRef는 useImperativeHandle와 무조건 함께 쓰일까?

그건 아니다!

forwardRef만 사용하고 싶을 때도 있다.

부모 컴포넌트에서 자식 컴포넌트에 ref를 이용하여 직접 연결만하고, DOM 요소에는 직접 접근이 가능하다

const Input = forwardRef<HTMLInputElement, TextFieldProps>(
  ({ label, ...props }, ref) => {
    return <input ref={ref} {...props} />;
  }
);

와 같이 사용을 하면, 부모 컴포넌트에서는 focus 함수를

const ParentComponent = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  return (
    <div>
      <TextField ref={inputRef} />
      <button onClick={() => inputRef.current?.focus()}>포커스</button>
    </div>
  );
};

위와 같이 사용할 수 있다.

그럼 언제 useImperativeHandle을 사용해야할까?

간단히 말하면, 부모에게 전달할 값이나 동작을 커스텀하고 싶을 때 사용한다.

특정 메소드만 노출하거나, ref가 특정 값을 반환하도록 제어할 때 유용하다.

const Input = forwardRef<HTMLInputElement, TextFieldProps>(
  ({ label, ...props }, ref) => {
    const inputRef = useRef<HTMLInputElement>(null);

    useImperativeHandle(ref, () => ({
      focus: () => {
        inputRef.current?.focus();
      },
      clear: () => {
        if (inputRef.current) inputRef.current.value = '';
      },
    }));

    return <StyledInput ref={inputRef} {...props} />;
  }
);

위와 같이 사용을 하면 부모 컴포넌트에서는 focus와 clear 메소드만 사용이 가능하다.

즉, 특정 메소드만 노출하거나, 동작을 제한하고 싶을때 useImperativeHandle을 사용한다.

그럼 forwardRef만 사용해도되는게 아닌가?

사실 나도 이렇게 생각하였다.

useImperativeHandle 훅을 왜 사용하지?라는 의문점을 가져, 이번에 공부를 하게 된 것이다.

useImperativeHandle이 필요한 경우에 대해 알아보자

  1. 불필요한 내부 값 노출 방지(캡슐화)
  • ref가 input의 DOM 요소를 직접 가리키면, 부모에서 inputRef.current.value = … 과 같은 방식으로 내부 상태를 강제로 조작할 수 있다.
  • 이렇게 되면, 컴포넌트 내부에서 관리해야하는 값들이 있을 때 useImperativeHandle을 이용하여 직접 노출되지 않도록 값을 제한한다!
  1. 다중 Ref 관리
  • 컴포넌트 내부에 여러 개의 ref가 있을 때, 부모가 특정 요소만 컨트롤하도록 만들고 싶을 수도 있다.
const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef<HTMLInputElement>(null);
  const buttonRef = useRef<HTMLButtonElement>(null);

  useImperativeHandle(ref, () => ({
    focusInput: () => inputRef.current?.focus(),
    clickButton: () => buttonRef.current?.click(),
  }));

  return (
    <div>
      <input ref={inputRef} {...props} />
      <button ref={buttonRef}>버튼</button>
    </div>
  );
});

const Parent = () => {
  const inputRef = useRef<{ focusInput: () => void; clickButton: () => void }>(null);

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={() => inputRef.current?.focusInput()}>입력창 포커스</button>
      <button onClick={() => inputRef.current?.clickButton()}>버튼 클릭</button>
    </div>
  );
};

위와 같이 같은 ref를 이용하여 특정 요소를 컨트롤하게 이용하였다.

  1. 외부 라이브러리 또는 커스텀 훅과 연동할 때
  • 외부 라이브러리 경우 특정 DOM을 다뤄야하는데, 부모 컴포넌트가 직접 ref로 다루면 내부 구현이 바뀌었을 때 문제가 생길 수 있으므로, 필요한 부분만 노출시켜 유지보수 용이하게 만들자!
728x90