React - ref

이환희·2021년 4월 6일

React

목록 보기
4/12

ref는 key와 마찬가지로 React의 독특한 prop이다.

<input ref={inputRef} />

이렇게 ref을 넘기면

inputRef.current를 통해 input 엘리먼트에 직접 접근할 수 있다.

ref prop에는 특정 형태의 객체(current 속성을 갖는)만을 할당할 수 있다.

  • 이 객체를 만드려면 클래스기반에서는 React.createRef()를
  • hook에서는 useRef()를 사용함
import React, { useRef } from "react"

function Field() {
  const inputRef = useRef(null)

  function handleFocus() {
    inputRef.current.disabled = false
    inputRef.current.focus()
  }

  return (
    <>
      <input disabled type="text" ref={inputRef} />
      <button onClick={handleFocus}>활성화</button>
    </>
  )
}

button 엘리먼트의 클릭(click) 이벤트 핸들러에서 inputRef.current로 간단하게 input 엘리먼트를 제어할 수 있다.

import React, { useRef } from "react"
import music from "./music.mp3"

function Player() {
  const audioRef = useRef(null)

  const handlePlay = () => {
    audioRef.current.play()
  }

  const handlePause = () => {
    audioRef.current.pause()
  }

  return (
    <>
      <figure>
        <figcaption>Eyes on You (Sting) - Network 415:</figcaption>
        <audio src={music} ref={audioRef}>
          Your browser does not support the
          <code>audio</code> element.
        </audio>
      </figure>
      <button onClick={handlePlay}>재생</button>
      <button onClick={handlePause}>중지</button>
    </>
  )
}

useRef() 훅(hook) 함수로 audioRef 객체를 생성한 후, 엘리먼트의 ref prop에 설정해주었다. 따라서, 이벤트 핸들러 함수 내에서는 audioRef.current를 통해 엘리먼트의 play()와 pause() 함수를 호출할 수 있습니다.

하지만!

import React, { useRef } from "react"

function Input({ ref }) {
  return <input type="text" ref={ref} />
}

function Field() {
  const inputRef = useRef(null)

  function handleFocus() {
    inputRef.current.focus()
  }

  return (
    <>
      <Input ref={inputRef} />
      <button onClick={handleFocus}>입력란 포커스</button>
    </>
  )
}

Field >> Input >> input 엘리먼트 순서로 ref를 전달한다.

그럴듯 해보이지만

ref는 일반 prop이 아니기 때문에 Input에서 받아올때 ref1과 같은 다른 이름을 사용해야함

→ 보기 힘들어진다.

forwardRef를 쓰자

위에 Input엘리먼트를 다음과 같이 forwardRef()함수로 감싸면 ref를 그대로 전달할 수 있다.

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

오디오 플레이어 예시

// Player.jsx
import React, { useRef } from "react"
import Audio from "./Audio"
import Controls from "./Controls"

function Player() {
  const audioRef = useRef(null)

  return (
    <>
      <Audio ref={audioRef} />
      <Controls audio={audioRef} />
    </>
  )
}

export default Player
  • useRef() 훅(hook) 함수로 audioRef 객체를 생성한 후,
  • 컴포넌트에는 ref prop으로,
  • 컴포넌트에는 audio prop으로 넘겨주고 있다.
/ Audio.jsx
import React, { forwardRef } from "react"
import music from "./music.mp3"

function Audio(prop, ref) {
  return (
    <figure>
      <figcaption>Eyes on You (Sting) - Network 415:</figcaption>
      <audio src={music} ref={ref}>
        Your browser does not support the
        <code>audio</code> element.
      </audio>
    </figure>
  )
}

export default forwardRef(Audio)
  • 컴포넌트에서 넘기는 ref 을 제대로 받으려면 컴포넌트는 forwardRef() 함수를 사용해야 한다
  • 그러면 두 번째 매개 변수를 통해 ref 객체가 넘어오게 되고, 내부에 있는 엘리먼트로 다시 넘겨(forward)줄 수 있다.
  • 이를 통해 부모 컴포넌트인 에서 자식 컴포넌트인 의 내부에 있는 엘리먼트에 직접 접근할 수 있게 된다.

→ forwardRef는 한 다리 건너서(할아버지가 손주에게) 엘리먼트의 속성에 접근할때 유용하다.

// Controls.jsx
import React from "react"

function Controls({ audio }) {
  const handlePlay = () => {
    audio.current.play()
  }

  const handlePause = () => {
    audio.current.pause()
  }

  return (
    <>
      <button onClick={handlePlay}>재생</button>
      <button onClick={handlePause}>중지</button>
    </>
  )
}

export default Controls
  • 일반 prop로 받은 audio는 굳이 forwadRef를 안써도 된다.
  • 하지만 얘도 audioRef 객체가 넘어온것이므로 current라는 속성을 이용하여 컨트롤 가능

디버깅 팁

forwardRef() 함수를 호출할 때 다음과 같이 익명 함수를 넘기면 브라우저에서 React 개발자 도구를 사용할 때 컴포넌트의 이름이 나오지 않아서 불편할 수가 있다.

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

해결법(3가지 방법중 편한거 택)

  1. forwardRef() 함수에 익명 함수를 대신에 이름이 있는 함수를 넘기기
const Input = forwardRef(function Input(props, ref) {
  return <input type="text" ref={ref} />
})
  1. forwardRef() 함수의 호출 결과로 기존 컴포넌트를 대체
function Input(props, ref) {
  return <input type="text" ref={ref} />
}

Input = forwardRef(Input)
  1. forwardRef() 함수의 호출 결과의 displayName 속성에 컴포넌트 이름을 설정
const Input = forwardRef((props, ref) => {
  return <input type="text" ref={ref} />
})

Input.displayName = "Input"

useImperativeHandle(ref, CB)

  • 자식 컴포넌트의 메서드를 호출할 수 있게 한다.
  • forwardRef() 와 함께 사용

예제

Child-Component

import React, {useState, useImperativeHandle, forwardRef } from 'react'

// props와 ref를 인자로 받음
function ChildCompo(props,ref){
  const [name, setName]=useState('');
  const [age, setAge]=useState(0);

  // 1번인자 : ref
  // 2번인자 : 현재 컴포의 값을 부모컴포가 접근할수 있는 CB함수를 전달
  useImperativeHandle(ref, () => ({
    addAge : value => setAge(age + value),
    getNameLength : ()=> name.length,
  }))

  return (
    <div>
      <p>{`name is ${name}`}></p>
      <p>{`age is ${age}`}></p>
      // ...
    </div>)
}
// 여기서 forwardRef를 씌워줌으로 ref 매개변수를 사용할수 있게됨
// 부모컴포넌트에서 useRef()를 사용하고 자식의 useImprerativeHandle에 전달한 객체를 사용해 값 수정 가능
export default forwardRef(ChildCompo)

Parent-Component

function ParentCompo () {
  const childCompoRef = useRef();
  const onClick = () => {
    if (childCompoRef.current) {
      console.log('current name length:', childCompoRef.current.getNameLength();)
      childCompoRef.current.addAge(5);
    }
  }
  return (
    <div>
      <Profile ref={profileRef} />
      <button onClick={onClick}>add age 5</button>
    </div>
  )
}

→ 자식컴포넌트의 ref를 이용해 메소드 호출이 가능해짐


Reference

0개의 댓글