React - Forwarding Ref

이소라·2022년 8월 15일
0

React

목록 보기
9/23

Forwarding Ref

  • Ref forwarding
    • 자식 컴포넌트로 ref를 자동으로 전달하는 기법
    • 재사용 가능한 컴포넌트 라이브러리와 같은 컴포넌트에서 유용함



DOM에 refs 전달하기

  • React 컴포넌트는 그들의 수행 상세 내용, 출력 결과를 숨김 (캡슐화)

    • 다른 컴포넌트에서 내부 DOM 요소에 대한 ref를 얻을 필요 없음
  • 이러한 캡슐화는 애플리케이션 레벨의 컴포넌트에게는 바람직하지만, 재사용성이 큰 말단 컴포넌트에서는 불변함

    • 말단 컴포넌트는 애플리케이션 전반적으로 일반적인 DOM의 button과 input과 비슷한 방법으로 사용됨
    • focus, selction, 애니메이션 등을 관리하기 위해서 이러한 DOM 노드에 접근하는 것이 불가피함
  • Ref forwarding은 일부 컴포넌트가 자신이 받은 ref를 자식 컴포넌트로 내려주는데 최적화되어 있음

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>
  • 위 코드에서 FancyButton은 React.forwardFef를 사용해서 전달받은 ref를 얻어서 렌더링되는 DOM button에 전달함

    • 이 방법을 통해 FancyButton을 사용하는 컴포넌트는 밑에 있는 button DOM 노드에 대한 ref를 얻을 수 있고, 필요한 경우 DOM button을 직접 사용하는 것 처럼 접근할 수 있음
  • 위 코드에 대한 단계적 설명

    1. React.createRef를 호출해서 React ref를 생성하고, ref 변수에 할당함
    2. JSX 속성으로 작성해서 ref를 <FancyButton ref={ref}> 아래로 전달함
    3. React는 forwardRef(props, ref) => ...의 두 번째 인자로 ref를 전달함
    4. JSX 속성을 작성하여 ref를 <button ref={ref}>로 내려서 전달해줌
    5. ref가 설정되면, ref.current 속성은 <button> DOM 노드를 가리킴
  • 두번째 인자 ref는 React.forwardRef 호출과 함께 컴포넌트가 정의되었을 때만 존재함
  • 일반적인 함수나 클래스 컴포넌트는 ref 인자를 받지도 않고, props에서 사용할 수 없음
  • ref forwarding은 DOM 컴포넌트 뿐만 아니라 클래스 컴포넌트의 인스턴스도 전달할 수 있음

주의 사항

  • 컴포넌트 라이브러리에서 forwardRef를 사용하기 시작할 때 변경사항으로 간주하고 라이브러리의 새로운 중요 버전으로 릴리즈 해야 함

    • 라이브러리에 보일 만큼 다르게 동작할 가능성이 높음(ref에 할당되는 것이나 내보내는 유형이 다를 수 있음)
    • 이전 동작에 의존하는 애플리케이션이나 다른 라이브러리들을 손상될 가능성이 큼
  • React.forwordRef가 존재할 때 조건부로 적용하는 것도 권장하지 않음

    • 라이브러리가 동작하는 방식을 변경할 수 있음
    • React가 업데이트될 때 애플리케이션이 손상될 수 있음



고차 컴포넌트(HOC)에서의 ref 전달하기

  • 고차 컴포넌트(HOC)에서 ref forwarding이 유용할 수 있음
function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }
    
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
  
  return LogProps;
}

class FancyButton extends React.Component {
  focus() {
    // ...
  }
  // ...
}
// FancyButton 대신 LogProps를 내보내도 결과적으로는 FancyButton이 렌더링 됨
export default logProps(FancyButton)
  • 위 코드에서 logProps 고차 컴포넌트는 모든 props를 고차 컴포넌트가 감싸고 있는 컴포넌트에 전달하므로 렌더링된 결과가 동일하게 됨
    • 주의사항 : ref는 prop이 아니므로 전달되지 않음
    • 고차 컴포넌트에 ref를 추가하면, 감싸진 컴포넌트가 아닌 가장 바깥쪽 컴포넌트를 참조함
import FancyButton from './FancyButton';

const ref = React.createRef();
// 가져온 FancyButton 컴포넌트는 LogProps HOC임
// ref는 내부 FancyButton 컴포넌트 대신 LogProps를 가리킴
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}
/>;
  • React.forwardRef API를 사용하여 내부 FancyButton 컴포넌트에 대한 refs를 명시적으로 전달받을 수 있음
    • React.forwardRefpropsref 매개변수를 받아 React 노드를 반환하는 렌더링 함수를 인수로 받음
function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }
    
    render() {
      const {forwordedRef, ...rest} = this.props;
      // 사용자가 정의한 prop 'forwordedRef'를 ref로 할당함
      return <Component ref={forwardedRef} {...this.props} />;
    }
  }
  // React.forwardRef에서 제공하는 두 번째 매개변수 ref를 주의하기
  // 'forwordedRef'와 같은 일반 prop으로 ref를 LogProps에 전달할 수 있음
  // 'forwordedRef' prop으로 전달받은 ref를 Component의 ref에 연결할 수 있음 
  return LogProps.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}



DevTools에 사용자 정의 이름 표시하기

  • React의 DevTools는 React.forwardRef가 인자로 받는 렌더링 함수를 사용하여 ref 전달 컴포넌트에 대해 무엇을 표시할 것인지 결정함
// DevTools에 'ForwardRef'로 나타남
const WrappedComponent = React.forwardRef((props, ref) => {
  return <LogProps {...props} forwardedRef={ref} />;
});
// DevTools에 'ForwardRef(myFunction)'으로 나타남
const WrappedComponent = React.forwardRef(
  function myFunction(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }
);
  • 감싸고 있는 컴포넌트를 포함하도록 함수의 displayName 속성을 설정 가능함
function logProps(Component) {
  class LogProps extends React.Component {
    // ...
  }

  function forwardRef(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }

  // DevTools에서 이 컴포넌트에 조금 더 유용한 표시 이름을 지정 가능함
  // 예, "ForwardRef(logProps(MyComponent))"
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}

0개의 댓글