forwardRef & useImperativeHandle

김동현·2021년 12월 7일
0

React

목록 보기
15/27
post-thumbnail

ref prop

상위 컴포넌트 내 useRef 훅 호출하여 반환된 객체를 하위 컴포넌트에게 ref 어트리뷰트로로 전달하는 것은 불가능합니다.

ref 어트리뷰트는 html 태그 이름을 JSX 문법으로 사용하는 곳에서만 작성 가능하며 돔 노드 객체를 직접 다루기 위해서 사용합니다.

만약 사용자 정의 컴포넌트에 ref 어트리뷰트를 사용하게 된다면 에러가 발생하게 됩니다.

// Parent.js
import { useRef } from 'react';
import Child from './Child.js';

const Parent = () => {
    const inputRef = useRef();
    
    // ref prop으로 Child 컴포넌트에게 inputRef 전달
    // ref prop은 데이터를 전달하는 용도로 사용할 수 없음
    // 돔 노드 객체와 연결하기 위한 용도로 사용됨
    return <Child ref={inputRef} />;
};
export default Parent;

Parent 컴포넌트는 Child 컴포넌트에게 ref 어트리뷰트값으로 useRef 훅 호출하여 반환된 inputRef 객체를 작성하여 전달하고 있습니다.

// Child.js
const Child = props => {
    return (
        <form>
            // inputRef 객체는 전달되지 않음
            <input ref={props.ref} />
        </form>
    );
};

export default Child;

ref 어트리뷰는 리액트에서 "특수한 목적"으로 사용되기 때문에 일반적인 데이터 전달하는 용도로 사용할 수 없는 prop입니다.

이렇게 특수한 목적을 갖는 어트리뷰트로는 리액트 엘리먼트를 고유하게 식별하기 위해 사용되는 key 어트리뷰트를 예로 들 수 있습니다. ref 어트리뷰트도 마찬가지로 "돔 노드 객체 접근"이라는 특수한 용도로 사용되기 때문에 데이터를 전달하기 위한 일반적인 prop으로 사용을 할 수 없습니다.

forwardRef

상위 컴포넌트에서 useRef 훅 호출로 반환된 객체를 하위 컴포넌트에 ref 어트리뷰트로 전달하려면 react 라이브러리에서 제공하는 "React.forwardRef 라는 함수"를 사용해야 합니다.

import { forwardRef } from 'react';

  // forwardRef의 임수로 컴포넌트를 전달
  // 인수로 전달한 컴포넌트는 props객체와 ref 어트리뷰트 값을 전달받음
const Child = forwardRef((props, ref) => {
    ,,,
    return ,,,;
});

export default Child;
  1. ref 어트리뷰트를 통해 값을 전달받을 하위 컴포넌트를 React.forwardRef 인수로 전달합니다.

  2. forwardRef 함수가 실행되면 첫 번째 인수로 "props 객체"를 전달받고, 두 번째 인수로 "ref 어트리뷰트로 전달된 값"을 전달받는 컴포넌트 함수를 반환합니다.

즉, forwardRef 함수의 인수로 컴포넌트 함수 전달시 props 객체상위 컴포넌트가 ref 어트리뷰트로 전달한 값을 인수로 전달받는 컴포넌트 함수를 반환합니다.

이를 통해 상위 컴포넌트는 하위 컴포넌트에 존재하는 돔 요소 노드를 상위 컴포넌트에서 직접 조작할 수 있습니다.


아래 코드처럼 Parent 컴포넌트에서 useRef 훅 호출하여 반환된 객체인 parentRef를 하위 컴포넌트인 Child 컴포넌트로 전달하기 위해서는 "Child 컴포넌트 함수를 React.forwardRef 함수의 인수로 전달"합니다. 그러면 "Child 컴포넌트 함수는 인수로 propsref를 상위 컴포넌트로부터 전달받을 수 있습니다".

// Parent.jsx
import { useRef } from 'react';

const Parent = () => {
    const parentRef = useRef();
    
    // 하위 컴포넌트인 Child에게 ref 어트리뷰트로 Childref를 전달
    return <Child ref={parentRef} />;
};

export default Parent;

Parent 컴포넌트가 ref 어트리뷰트로 전달한 parentRef 객체를 Child 컴포넌트의 html 태그 이름을 사용한 JSX 문법에 ref 어트리뷰트 값으로 설정하여 parentRef.current에 돔 노드 객체를 연결시킬 수 있습니다.

// Child.jsx
import { forwardRef } from 'react';

  // forward 함수의 인수로 컴포넌트 함수 전달시
  // 함수 컴포넌트는 props와 ref를 인수로 전달받음
const Child = React.forwardRef((props, ref) => {
    return <input ref={ref} />;
});

export default Child;

즉, 상위 컴포넌트에서 useRef 훅으로 생성한 객체와 하위 컴포넌트의 돔 노드 객체 연결하기 위해서 하위 컴포넌트 함수를 React.forwardRef 함수의 인수로 전달해야 합니다.

useImperativeHandle

useImperativeHandler 훅은 forwardRef와 함께 사용해야 합니다.

useImperativeHandler 훅은 하위 컴포넌트에서 돔 노드 객체와 관련된 로직을 작성하고 상위 컴포넌트에게 노출하고 싶은 값들을 상위 컴포넌트의 useRef 훅 호출시 반환된 객체의 current 프로퍼티에 바인딩되도록 도와줍니다.

  1. forwardRef 함수의 인수로 하위 컴포넌트 함수를 전달하면서 호출시 첫 번째 인수로 props 객체를 전달받고 두 번째 인수로는 ref 어트리뷰트 값을 전달 받는다.

  2. 하위 컴포넌트 내부에서는 상위 컴포넌트가 ref 어트리뷰트로 전달한 값을 html 태그 이름으로 JSX 문법으로 사용한 곳에 ref 어트리뷰트 값으로 작성하여 연결

  3. 하위 컴포넌트 내부에서 useImperativeHandler 훅의 첫 번째 인수로는 상위 컴포넌트가 ref 어트리뷰트로 전달한 값을 전달하고, 두 번째 인수로는 콜백 함수를 전달합니다.

  4. 콜백 함수의 반환값이 바로 상위 컴포넌트 내 useRef 훅 호출시 반환된 객체의 current 프로퍼티에 바인딩될 값이 됩니다.


// Parent.js
import { useRef } from 'react';

import Child from './child';

const Parent = () => {
    const parentRef = useRef();
    
    const clickHandler = () => {
        console.log(ref.current); // -> { clickHandler: f }
    }
    
    return (
        <>
            <Child ref={ref} />
            <button>Parent Component button</button>
        </>
    );
}

export default Parent;

Parent 컴포넌트에서 useRef 훅으로 생성한 parentRef 객체를 Child 컴포넌트의 ref 어트리뷰트 값으로 작성합니다.

// Child.js
import { forwardRef, useImperativeHandler } from 'react';

const Child = forwardRef((props, ref) => {
    const clickHandler = () => {
        console.log(ref.current); // -> { clickHandler: f }
    }

    useImperativeHandler(ref, () => {
        Console.log(ref.current); // ->DOM 요소 노드
        return { clickHandler };
        // 해당 함수 실행된 이후 ref.current는 { clickHandler: f}가 바인딩된다
    }
    
    return <button ref={ref}>Child Component button</button>;
};

export default Child;

Child 컴포넌트는 ref 어트리뷰트로 전달받은 객체를 html 태그 이름을 사용한 JSX에 ref 어트리뷰트 값으로 작성하여 서로 연결합니다.

그리고 Child 컴포넌트에서 useImperativeHandler 훅을 호출할 때 첫 번째 인수로는 Parent 컴포넌트가 ref 어트리뷰트로 전달한 값을 전달하고, 두 번째 인수로는 콜백 함수를 작성합니다.

이후 useImperativeHandler 훅이 실행되고 Parent 컴포넌트 내 parentRef.current 에는 콜백 함수의 반환값과 서로 연결됩니다.

참고로 콜백 함수 내부에서는 Parent 컴포넌트가 ref 어트리뷰트로 전달한 객체의 current 프로퍼티가 연결된 돔 노드 객체를 가리키지만, 콜백 함수 외부에서는 콜백 함수의 반환값과 서로 연결되어 있습니다.


useImperativeHandler 훅은 반드시 forwardRef와 함께 사용해야 하며, forwardRef상위 컴포넌트에서 useRef 훅으로 생성한 객체를 하위 컴포넌트 내 html 태그 이름을 사용한 JSX와 반드시 연결해야 합니다.

useImperativeHandler 훅의 첫 번째 인수로는 상위 컴포넌트가 ref 어트리뷰트로 전달한 객체를 전달하고, 두 번째 인수로는 콜백 함수를 전달합니다.
이때 콜백 함수내 ref.current는 돔 노드 객체와 연결되어 있습니다. 이후 콜백 함수가 실행되면 ref.current는 콜백 함수가 반환값으로 작성한 값과 연결됩니다.

즉, 하위 컴포넌트에 useImperativeHandler 훅 두 번째 인수로 전달한 콜백 함수 내부에 돔 노드 객체와 관련된 로직을 작성하고, 외부에 노출하고 싶은 로직을을 반환값으로 작성합니다.
이후 상위 컴포넌트에서는 useRef 훅 호출시 반환된 객체의 current 프로퍼티에는 콜백 함수의 반환값이 바인딩되어있으므로 이에 접근하여 하위 컴포넌트에 존재하는 돔 노드 객체를 조작할 수 있습니다.

profile
Frontend Dev

0개의 댓글