[React] useRef and useImperativeHandle

김병진·2021년 3월 2일

React

목록 보기
2/2
post-thumbnail

Prologue


부모 -> 자식 방향 데이터 전달은 props를 통해 쉽게 가능합니다.
그보다 조금 더 복잡한 자식 -> 부모 방향 데이터 전달에 대해 알아보겠습니다.
구글링을 하다보니 함수 안에 함수를 두어 props로 넘기는 복잡한 구조도 있지만 이번에 소개할 hook은 useRef와 useImperativeHandle 입니다.

useRef


useRef는 아래와 같은 인터페이스를 갖고 있습니다.

interface MutableRefObject<T> {
   current: T;
}
function useRef<T = undefined>(): MutableRefObject<T | undefined>;

useRef를 통해 변수를 선언하고 사용할 수 있습니다. 다만, useState와 다른 점은 렌더링 이후에도 기존 값을 가지고 있다는 점입니다.
변수에 할당된 값은 MutableRefObject 인터페이스를 따르는데 원하는 값을 current 에 할당하여 관리할 수 있습니다.

리액트에서는 리렌더링 되는 조건은 아래와 같습니다.

  1. Props의 변경
  2. State의 변경
  3. forceUpdate() 실행
  4. 부모 컴포넌트의 렌더링

react로 코딩하다보니 렌더링에 영향을 받지 않는 변수를 찾게 됩니다.
예를 들어, 렌더링 이전 값을 기억해야 하는 경우 혹은 자식 컴포넌트에서 부모 컴포넌트의 기능을 이용하는 경우 등 입니다.

import {useEffect, useRef} from "react";

export function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

위 코드는 useRef와 useEffect hook을 이용해서 만든 custom hook 입니다.
이 hook 을 이용하면 렌더링에 상관없이 값을 보존하고 특정 조건에만 변경하여 원하는 기능을 구현할 수 있습니다.

useImperativeHandle


useImperativeHandle은 ref를 사용할 때 부모 컴포넌트에 노출되는 인스턴스 값을 사용자화합니다. 대부분의 경우 ref를 사용한 명령형 코드는 피해야 합니다. useImperativeHandle는 forwardRef와 더불어 사용해야 한다고 React 공식 홈페이지에 적혀있습니다.

useImperativeHandle의 인터페이스는 아래와 같습니다.

function useImperativeHandle<T, R extends T>(
	ref: Ref<T> | undefined, 
 	init: () => R, 
 	deps?: DependencyList
): void;

첫 번째 인자인 ref는 Ref 객체를 받습니다.
두 번째 인자인 init은 함수를 받는데 이번 예제에서 부모 컴포넌트에서 쓰일 함수를 선언하고 내용을 채웁니다.
세 번째 인자인 deps는 useCallback의 deps처럼 deps 배열 안에 있는 변수의 상태가 바뀌면 useImperativeHandle이 재선언됩니다.

인터페이스만 보면 이해하기가 쉽지 않습니다.
쉽게 아래의 예제를 통해 이해하시면 좋겠습니다.

    useImperativeHandle(ref, () => ({
      handleNextStep () {
        const updated: ReducerAutomationDto = update(singleAutomation, {
          start: { $set: [ automation[0] ] },
          end: { $set: [ automation[1] ] },
        })
        dispatch(controlAutomation(updated));
      }
    }))

위 예제는 Stepper인 부모 컴포넌트에서 다음 버튼을 클릭할 경우, 자식 컴포넌트에서 데이터를 store에 dispatch 하는 함수입니다.

Stepper(부모) --> Slider(자식) 구조입니다.

저의 경우 Stepper 안에 Slider를 두었습니다. 다음 혹은 뒤로 갈 경우, 값이 그대로 남아있길 바랐기 때문에 Redux를 이용해야 했습니다.

Slider 컴포넌트에서 값이 변할 때마다 Redux에 저장을 해주어야 다음 혹은 뒤로 넘어가도 값이 보존될 수 있습니다. 하지만 이 방법은 리소스를 낭비하게 됩니다.

이 경우 useRef와 useImperativeHandle을 이용하면 좋습니다. 부모 컴포넌트인 Stepper에서 다음을 누를 경우만, 자식 컴포넌트인 Slider에서 값을 Redux에 저장할 수 있습니다.

결국 다음 혹은 뒤로를 클릭할 경우, Redux에 저장하는 로직으로 바꾸어 최적화하였습니다.

Conclusion


DOM 객체에 접근하기 위해 리액트는 ref를 만들어 사용합니다. 그래서 useRef는 렌더링에 영향을 받지 않고 변수를 관리할 수 있고 DOM 객체에 대한 직접적인 참조 주소를 반환 받아 HTML DOM 메소드 및 속성을 이용할 수 있습니다.

useImperativeHandle는 forwardRef와 사용 가능하며 부모는 자식의 DOM에 직접적으로 접근을 하는 것이 아니라 useImperativeHandle로 전달된 메서드에만 접근이 가능해집니다. 이로서 더욱 컴포넌트 간의 독립성을 보장할 수가 있습니다.

profile
아이디어를 구현할 수 있는 개발자가 목표입니다.

0개의 댓글