ziglog

    Search by

    왜 forwardRef를 사용할까?

    July 18, 2021 • ☕️ 3 min read

    React의 forwardRef


    react-hook-form은 기본적으로 ref를 심어주는 비제어 컴포넌트를 사용한다. useForm에서 리턴 받은 register를 input의 ref에 담아, input의 이름으로 값을 가져온다.

    Copy
    const { register } = useForm()
    
    <input name="firstName" ref={register} />

    그런데 사용하는 input이 만약 React component로 커스터마이징한 input이라면, ref를 다시 해당 input 컴포넌트의 실제 input 태그로 전달해줘야 한다.

    Copy
    // Upload.tsx
    const { register, handleSubmit, setValue, watch } = useForm<FeedToUpload>();
    
    const Upload = () => {
      return (
        <Styled.VerticalWrapper>
          <Label text="제목" required={true} />
          <FormInput {...register("title", { required: true })} />
        </Styled.VerticalWrapper>
      );
    };

    FormInput이 아래와 같이 구현되어 있을 때,

    Copy
    // FormInput.tsx
    const FormInput = ({ ...options }) => {
      return <Styled.Root {...options} />;
    };

    Upload 페이지에서 사용하는 FormInput 컴포넌트에 스프레드({...options})해준 props로 ref가 자동으로 넘어갈 것이라고 생각했으나, 그렇게 작성하지 말라는(!) 오류가 발생한다. 일반적인 방식의 props가 아니라고 경고하는 것이다.

    이때 아래와 같이 props를 다른 이름(여기서는 formRef)으로 내려주면 에러는 해결된다.

    Copy
    const Upload = () => {
      return (
        <Styled.VerticalWrapper>
          <Label text="제목" required={true} />
          <FormInput name="title" formRef={register} />
        </Styled.VerticalWrapper>
      );
    };

    하지만 우리는 ref의 값을 예측 가능하게끔 만들기 위해 ref라는 이름으로 내려주고 싶다!

    여기서 forwardRef가 등장한다. forwardRef는 말 그대로 ref를 밀어준다(?)는 의미로, 부모 컴포넌트의 ref를 렌더링하는 자식 컴포넌트로 밀어준다. forwardRef를 사용하면 자식 컴포넌트는 함수의 두 번째 인자로 ref를 받게 된다. ref는 props와 별개로 취급되고 있다.

    Copy
    type Props = InputHTMLAttributes<HTMLInputElement>;
    
    const FormInput = React.forwardRef<HTMLInputElement, Props>(
      ({ ...options }, ref) => {
        return <Styled.Root ref={ref} {...options} />;
      }
    );

    TypeScript를 사용하는 경우 타이핑은 아래와 같이 작성해준다. 첫 번째 타입으로 RefType을, 두 번째 타입으로 PropsType을 넣어준다.

    Copy
    const Component = React.forwardRef<RefType, PropsType>((props, ref) => {
      return someComponent;
    });

    createRef vs useRef

    클래스 컴포넌트에서는 createRefref를 생성한다. 보통 컴포넌트의 인스턴스가 생성될 때 ref를 프로퍼티로서 추가하고, 그럼으로써 컴포넌트의 인스턴스의 어느 곳에서도 ref에 접근할 수 있게 된다.

    Copy
    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.myRef = React.createRef();
      }
      render() {
        return <div ref={this.myRef} />;
      }
    }

    함수 컴포넌트에서도 createRef를 사용할 수는 있다. 하지만 함수 컴포넌트는 클래스형 컴포넌트와 달리 인스턴스를 생성하지 않기 때문에 리렌더링될 때마다 createRef 함수는 새로 호출될 것이며, 이에 따라 createRef로 생성된 ref 값이 새로 생성된다. 컴포넌트의 생애주기 내에서 값을 유지하지 못하는 것이다.

    따라서 함수 컴포넌트에서는 useRef라는 hook을 사용하여 컴포넌트 생애주기 전체에서 값을 유지해준다. useRef로 생성한 ref 객체는 컴포넌트의 렌더링 시 ref.current의 값을 유지해주게 된다.

    Copy
    function CustomTextInput(props) {
      const textInput = useRef(null);
    
      function handleClick() {
        textInput.current.focus();
      }
    
      return (
        <div>
          <input type="text" ref={textInput} />
          <input type="button" value="Focus the text input" onClick={handleClick} />
        </div>
      );
    }

    Ref


    Relative Posts:

    UI runtime으로서의 리액트

    September 26, 2021

    ModalProvider 만들기

    July 18, 2021

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon