Front-End(Web)/React - 프레임워크(React, Next)

[React] 자식 컴포넌트 제어 - forwardRef, useImperativeHandle

ttaeng_99 2022. 9. 15. 04:37
반응형

사이드 프로젝트에서 Uncontrolled Input을 구현하기 위해 고민하던 중,

문득 자식(input)의 ref.current.value를 부모(container component)가 어떻게 참조할 수 있을까에 대한 의문이 생겼다.

* ref, useRef() 포스팅 : https://abangpa1ace.tistory.com/248

 

[React] Hooks - useRef()

회사에서는 Vue를 주로 사용하다보니, 개인공부나 토이 프로젝트에서는 React를 꾸준히 적용하고있다. 이번에 MBTI 사이드 프로젝트를 진행하면서, 페이지 컴포넌트에서 질문 리스트를 불러와서

abangpa1ace.tistory.com

 

가장 심플하게, 자식인 React 노드에 ref를 줘보았고, 아래와 같은 에러 메세지를 띄워줬다.

React에서 ref는 기본적으로 HTML 엘리먼트를 참조하기 위한 props이기에, 컴포넌트 props로는 기본적으로 사용이 불가하다.

(React에서 key, ref 등 일부 키값들은 특수한 용도로 사용되기 때문에 일반적인 props로 사용할 수 없다.)

 

이러한 경우에, forwardRef() 메서드를 사용하게 될 것이다.

 


 

📘 forwardRef() 란?

React 공식문서는, 일부 컴포넌트가 수신한 ref를 받아 조금 더 아래로 전달하는 Opt-in 기능이라고 소개한다. (링크)

 

Ref 전달하기 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

React 컴포넌트를 forwardRef() 메서드로 감싸면, 컴포넌트 함수는 추가적으로 두 번째 매개변수로 ref를 받을 수 있게 된다.

아래는, <Field> 부모 컴포넌트가 <Input> 자식 컴포넌트를 참조하기 위해 forwardRef() 가 적용된 예시이다.

import { useRef, forwardRef } from "react";

const Input = forwardRef((props, ref) => {
    return <input type="text" ref={ref} />;
});

const Field = () => {
    const inputRef = useRef(null);
    const focusHandler = () => {
    	inputRef.current.foucus();
    };
    
    return( 
    	<> 
            <Input ref={inputRef} /> 
            <button onClick={focusHandler}>focus</button> 
        </> 
    ); 
};
  1. 자식 컴포넌트(Input)를 React의 forwardRef() 로 감싼다. 두 번째 인자인 ref를 참조하려고 하는 엘리먼트에 할당한다.
  2. 부모 컴포넌트(Field)에서 참조를 위한 useRef() 를 생성한다. (클래스형 컴포넌트의 경우엔 React.createRef())
  3. 참조를 위한 ref값(inputRef)을 자식 컴포넌트의 props로 할당해준다. 별도의 에러가 발생하지 않는다.

 


 

📘 useImperativeHandle() Hooks

useImperativeHandle() 은 나에게도 그렇지만 매우 생소한 훅일 것이다. 우선, 공식문서는 이 훅을 아래와 같이 설명한다.

useImperativeHandle customizes the instance value that is exposed to parent components when using ref...
useImperativeHandle should be used with forwardRef

부모 컴포넌트에 노출된 인스턴스(컴포넌트?)를 값을 커스텀한다. ref를 활용하므로, forwardRef와 함께 쓰이는 듯 하다.

 

더 자세히 설명하자면, Child 컴포넌트의 상태를 Parent 컴포넌트에서 제어하기 위한 훅이라고 이해하면 된다.

보통 부모 컴포넌트에서 자식의 상태값에 접근하거나 메서드를 직접 실행하기 위해 사용된다.

 

- 문법

useImperativeHandle(ref, createHandle, [deps])
  • ref : 프로퍼티를 부여할 ref. 보통 forwardRef() 로 부모에서 받아온 ref를 할당
  • createHandle : ref에 추가할 상태 혹은 메서드들의 객체를 반환하는 함수
  • deps : useImpreativeHandle 훅이 재정의되어야 하는 조건들

 

- 예제

// Child.tsx

import React from 'react';

const Child = React.forwardRef((props, ref) => {
  const childRef = useRef<HTMLInputElement>(null);
  
  useImperativeHandle(ref, () => ({
    focus: () => childRef.current.focus(),
    getValue: () => childRef.current.value,
  })
  
  return (
  	<form>
      <input ref={childRef} />
    </form>
  )
})
// Parent.tsx

import React from 'react';
import Child from './Child.tsx';

const Parent = () => {
  const parentRef = useRef<HTMLInputElement>(null);
    
  const focusChildByParent = () => {
    if (parentRef.current) parentRef.current.focus();
  }
  
  const getValueByParent = () => {
    if (parentRef.current) parentRef.current.getValue();
  }
  
  return (
    <div>
      <Child ref={parentRef} />
      <button onClick={focusChildByParent}>Set Focus!</button>
      <button onClick={getValueByParent}>Get Value!</button>
    </div>
  )
}
  1. 먼저, Child 컴포넌트forwardRef, useImperativeHandle API를 각각 세팅한다.
    forwardRef() 에서 받아온 ref가 useImperativeHandle 훅에 할당되면 된다.
  2. Parent 컴포넌트Child 컴포넌트에 ref를 등록한다. 이 ref를 통해 Child 컴포넌트의 상태와 메서드를 사용할 수 있다.

useImperativeHandle() 훅은 Child 컴포넌트가 주요한 상태나 로직을 가지고 있는 경우, 효과적으로 활용될 수 있을거라 생각된다.

특히, React는 부모 -> 자식 단방향 통신이기에, 이러한 훅은 구지 전역상태로 우회하지 않고도 자식을 효과적으로 제어할 수 있는 것이다.

 

다만, React에서 ref의 사용은 지양하므로, 라이브러리를 wrapping해서 사용하거나 부모가 자식의 메서드를 제어해야하는 경우 선택적으로 사용하면 된다.

 


 

forwardRef의 사용목적과 방법이 생각보다 간단하여 포스팅이 길어지진 않았다.

내용을 보강하는 목적도 있고 최근에 팀장님께 듣게 되어 useImperativeHandle() 훅까지 정리해보았는데,

forwardRef() 와의 연관성도 있을 뿐더러 이전에 메서드를 억지로 Parent로 올렸던 경험을 생각하면 유용하게 사용될 수 있는 훅이라 생각된다.

 

 

📎 출처

- [forwardRef 개념] React 공식문서 : https://ko.reactjs.org/docs/forwarding-refs.html  

- [forwardRef 사용법] daleseo 님의 블로그 : https://www.daleseo.com/react-forward-ref/  

 

- [useImperativeHandle 개념] React 공식문서 : https://ko.reactjs.org/docs/hooks-reference.html#useimperativehandle  

- [useImperativeHandle 개념] kelly-kh-woo 님의 블로그 : https://kelly-kh-woo.medium.com/react-hook-useimperativehandle-89fee716d80

- [useImperativeHandle 예제] soomst 님의 블로그 : https://soomst.tistory.com/entry/useImperativeHandle

반응형