함수 컴포넌트에 ref 전달하기

Jinjer·2022년 1월 12일
2
post-thumbnail

1. 프롤로그

input, select, button 등 폼 데이터를 다루기 위해
자주 사용되는 요소들이 있다

이러한 엘리먼트에 styled-components로 스타일 구성을 하거나
혹은 커스텀 훅을 사용하여 상태를 관리할 수 있도록
직접 함수 컴포넌트를 만들어 사용한다

🤔🤔🤔

그렇다면
ref를 함수 컴포넌트에서 어떻게 사용하는지 알아보자

2. ref 이해하기

ref가 왜 필요한지 잠시 살펴보자면
(알고 있다면 pass 하도록 하자👏)

기존 자바스크립트에서는
id, class, name, data 등 속성을 지정하여
getElementById와 같은 메서드를 사용하여 선택자로 선언할 수 있었다

ref란
리액트에서 DOM 엘리먼트직접 접근하여 여러가지 작업이 필요할 때
엘리먼트에 이름을 달아두고 사용한다고 생각하면 이해하기 쉽다

2-1. 기본적인 사용법

ref를 지정하면 해당 엘리먼트를 .current 객체로 받아 조작할 수 있다
useRef(Hooks API)를 사용하면 간단하게 적용할 수 있다!

2-2. 리렌더링 방지

일반적으로
리액트의 컴포넌트는 state가 변경되면 리렌더링이 발생되지만

const Main = () => {
  const [initValue, setInitValue] = useState({
    price: "",
    qty: ""
  });
  
  console.count("렌더링"); // 렌더링 시 콘솔 출력
  
  const handleInput = (e) => {
    const {name, value} = e.target;
    setInitValue(prevState => ({
      ...prevState,
      [name]: value
    }));
  };
  
  return (
    <>
      <input 
        type="number" 
        name="price"
        placeholder="금액"
        value={initValue.price}
        onChange={handleInput} 
      />
      <input 
        type="number" 
        name="qty"
        placeholder="수량"
        value={initValue.qty}
        onChange={handleInput} 
      />
      <input 
        type="number" 
        name="total"
        placeholder="합계"
        value={initValue.price * initValue.qty}
      /> 
    </>
  )
}

// 렌더링: 1
// 렌더링: 2
// 렌더링: 3
// ...

ref 객체의 .current 속성을 변형하는 것은 렌더링에 영향을 주지 않는다.
이로 인하여 값을 보유하거나 변경해야 하는 목적에 적합하다

const Main = () => {
  const inputPriceRef = useRef("");
  const inputQtyRef = useRef("");
  const inputTotalRef = useRef("");
  
  const handleInput = () => {
    inputTotalRef.current.value = inputPriceRef.current.value * inputQtyRef.current.value;
  };
  
  return (
    <>
      <input 
        type="number" 
        name="price"
        placeholder="금액"
        ref={inputPriceRef}
        onChange={handleInput}
      />
      <input 
        type="number" 
        name="qty" 
        placeholder="수량"
        ref={inputQtyRef}
        onChange={handleInput}
      />
      <input 
        type="number" 
        name="total" 
        placeholder="합계"
        ref={inputTotalRef}
      />
    </>
  )
}

3. ref 전달하기

리액트 공식문서에 의하면
함수 컴포넌트는 애초에 인스턴스가 없기 때문에 ref 속성을
사용할 수 없다고 안내하고 있다.

😨😨😨

그렇다면
정녕 방법이 없는걸까

3-2. forwardRef

물론 있다.
forwardRef로 함수 컴포넌트에 ref를 받아올 수 있다

import React, {forwardRef} from "react";

const Input = forwardRef((props, ref) => {
  return (
    <>
      <input
        type={props.type}
        id={props.id}
        name={props.name}
        value={props.value}
        ref={ref}
      />
    </>
  );
});

export default Input;

좋다.
ref를 제대로 받아오는 것을 확인할 수 있다

3-2. useImperativeHandle

그렇다면
부모 컴포넌트에서도 자식요소의 ref를 호출할 수 도 있을까?

import Import from "./Input";

const inputRef = useRef("");

return (
  <Input
    type="text"
    name="input"
    ref={inputRef}
  />
  <button type="button" onClick={() => inputRef.current.onFocus()}>focus!</button>
)

있다.
useImperativeHandle 훅을 사용하면 가능하다.

import React, {forwardRef, useImperativeHandle} from "react";

const Input = forwardRef((props, ref) => {
  const inputRef = useRef();
  
  useImprativeHandle(ref, () => ({
    onFocus: () => {
      inputRef.current.focus();
    }
  }));
  
  return (
    <>
      <input
        type={props.type}
        id={props.id}
        name={props.name}
        value={props.value}
        ref={ref}
      />
    </>
  );
});

export default Input;

이제 부모 컴포넌트에서 forwardRef가 적용된 자식요소의 ref에
접근하여 .current 속성을 원하는대로 사용할 수 있습니다.

3-3. 전체 예제 코드

📁 Home.js

import React, {useRef} from "react";
import Import from "./Input";

const Home = () => {
  const inputRef = useRef("");
  const onCreate = () => {
    inputRef.current.onFocus();
    inputRef.current.value = "Ref";
  }
  return (
    <Input
     type="text"
     name="input"
     ref={inputRef}
      />
    <button type="button" onClick={onCreate}>Here!</button>
  )
}  
export default Home;  

📁 Input.js

import React, {forwardRef, useImperativeHandle} from "react";

const Input = forwardRef((props, ref) => {
  const inputRef = useRef();
 
  useImprativeHandle(ref, () => ({
     value: props.value,  
     onFocus: () => {
       inputRef.current.focus();
     }
  }));
 
  return (
    <>
      <input
        type={props.type}
        id={props.id}
        name={props.name}
        value={props.value}
        ref={ref}
     />
   </>
 );
});

export default Input;

4. 정리

✔ ref가 왜 필요한가 알 수 있다
✔ 함수 컴포넌트에 ref를 전달할 수 있다
✔ 상태 관리를 위해 ref 남용은 자제해야 한다

profile
프론트엔드개발자

0개의 댓글