함수형 컴포넌트 ref

Plato·2022년 8월 19일
0

서론

일반적으로, 상위 컴포넌트는, 하위 컴포넌트에 props를 넘겨서, 하위 컴포넌트에 영향을 미친다. 하지만 상황에 따라서, props를 넘기지 않고, 컴포넌트 인스턴스에 접근하거나, 특정 DOM 요소에 접근하고 싶을 수 있다. 이를, ref로 구현하는 방법을 살펴보자.

본론

단어 설명

리액트 컴포넌트, 인스턴스, 요소

리액트 컴포넌트, 인스턴스, 요소에 대한 설명은, 이 글의, '단어 설명' 섹션에 있다.

value(값), reference(참조)

값을 갖는것과 대상을 참조하는 건 다르다.
이는 위키피디아의 "Value type and reference type" 문서에 잘 설명돼있다.

ref

ref는 'current 프로퍼티를 갖는 자바스크립트 객체'를 일컫는 말이다. ref를 사용하는 이유는 많다. 그중에 하나는, DOM 요소나 컴포넌트의 인스턴스를 참조하도록 만들면, DOM 요소와 인스턴스에 접근하고 제어할 수 있기 때문이다.
이때 함수형 컴포넌트는, 인스턴스가 없기 때문에, 인스턴스를 참조하도록 만들 수 없다. 그렇기에, ref.current가 컴포넌트의 인스턴스를 참조하도록 만드는 패턴은, 클래스형 컴포넌트에서만 사용할 수 있다. 이는 아래에서 더 자세히 다루겠다.
함수형 컴포넌트의 경우, useRef() 함수를 사용하여 ref 객체를 생성할 수 있고, 클래스형 컴포넌트의 경우, createRef() 함수를 사용하여 ref 객체를 생성할 수 있다.

함수형 컴포넌트에서, ref 객체 생성하기

ref 객체를 생성하기 위해서는, 아래와 같이 useRef() 함수를 호출해야 한다.

function FancyInput(props) {
  const inputRef = useRef();
  return <input />;
}

useRef(initialValue)는, current 프로퍼티가 initialValue를 갖거나 참조하는, ref 객체를 반환한다. 만약 initialValue를 넣지 않으면, ref 객체의 current 프로퍼티는 undefined 값을 갖는다.
위의 코드에서, inputRef를 콘솔에 프린트하면 아래와 같은 결과를 얻을 수 있다.

{current: undefined}

자바스크립트 객체이며, current 프로퍼티가 undefined값을 갖는 것을 확인할 수 있다.

ref로 DOM 요소 참조하기

ref 객체의 current 프로퍼티가, DOM 요소를 참조하도록 만들고 싶다면, '해당 DOM 노드를 설명하는 리액트 요소'를 만들 때, 아래와 같이 ref를 주면 된다.

function FancyInput(props) {
  const inputRef = useRef();
  useEffect( () => {
    console.log(inputRef)
  }, [] )
  return <input ref={inputRef}/>;
}

"return < input ref={inputRef}/ >"에 주목하자. 이처럼, props/attribute를 전달하는 것과 유사한 방식으로, ref를 줄 수 있다.
이렇게 'DOM 노드를 설명하는 리액트 요소'에 ref를 주면, inputRef의 current 프로퍼티는, input 요소를 참조한다.
이때 유의할 점은, 리액트 요소가 아니라 DOM 노드를 참조한다는 점이다. 그래서 위의 코드를 실행시키면, 아래와 같이 콘솔에 출력된다.

{current: input}
current: input
value: ""
__reactEvents$jnofpfha04h: Set(1) {'invalid__bubble'}
__reactFiber$jnofpfha04h: FiberNode {tag: 5, key: null, elementType: 'input', type: 'input', stateNode: input, …}
__reactProps$jnofpfha04h: {}
_valueTracker: {getValue: ƒ, setValue: ƒ, stopTracking: ƒ}
_wrapperState: {initialChecked: undefined, initialValue: '', controlled: false}
accept: ""
accessKey: ""
align: ""
alt: ""
ariaAtomic: null
ariaAutoComplete: null
ariaBusy: null
ariaChecked: null
ariaColCount: null
ariaColIndex: null
...

위의 결과는, console.dir(document.querySelector('input'))와 동일하다.
이를 통해, ref.current 프로퍼티가 input DOM 요소를 참조하고 있음을 확인할 수 있다.

해당 input 요소가 다른 값을 갖도록 만들고 싶다면, props를 전달할수도 있지만, 아래와 같이 해당 input DOM 요소의 value 프로퍼티를 직접 수정할수도 있다.

function FancyInput(props) {
  const inputRef = useRef();
  useEffect( () => {
    inputRef.current.value='props를 전달하지 않아도, 값을 바꿀 수 있다.'
  }, [] )
  return <input ref={inputRef}/>;
}

하위 컴포넌트에 ref 전달하기

클래스형 컴포넌트는 인스턴스가 존재하고, 상태와 props를 인스턴스가 담고있기 때문에, 해당 인스턴스를 참조하게 만들면, 해당 인스턴스의 상태를 바꿀 수 있고, props도 읽을 수 있다.

하지만, 함수형 컴포넌트는 인스턴스가 존재하지 않기 때문에, 함수형 컴포넌트의 인스턴스를 참조할 방법은 없다.
그러나, 함수형 컴포넌트에 ref를 전달하는 건 가능하다.
예시를 살펴보자.

function App() {
  const myRef = useRef()

  return (
    <div className="App">
      <FancyInput ref={myRef}/> //오류가 발생한다.
    </div>
  );
}

function FancyInput(props, ref) {
  const inputRef = useRef();
  useEffect( () => {
    inputRef.current.value='props 전달 안하고도, 값을 바꿀 수 있다.'
  }, [] )
  return <input ref={inputRef}/>;
}

실행시키면, 아래와 같은 오류가 출력된다. "Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?"

함수형 컴포넌트인 FancyInput에 ref를 주면서 발생하는 오류다. 오류에서 forwardRef()함수를 사용하길 권장하는데, 이를 통해 FancyInput에 ref를 전달해보자.

function FancyInput(props, ref) {
  const inputRef = useRef();
  useEffect( () => {
    inputRef.current.value='props 전달 안하고도, 값을 바꿀 수 있다.'
  }, [] )
  return <input ref={inputRef}/>;
}

FancyInput = forwardRef(FancyInput);

함수형 컴포넌트를, forwardRef 함수의 파라미터로 넘겨주면 된다.
이전에 'DOM node를 설명하는 리액트 요소'에 ref를 줬더니, ref의 current 프로퍼티가 해당 node를 참조했다.
그러면, '함수형 컴포넌트에 ref를 넘기면, ref가 컴포넌트를 참조할 것'이라 생각하기 쉽다. 하지만 아쉽게도, ref가 참조하는 값이 변경되지 않는다.

function App() {
  const myRef = useRef()
  useEffect( () => {
    console.log(myRef.current) // undefined를 출력한다.
  }, [] )
  return (
    <div className="App">
      <FancyInput ref={myRef}/>
    </div>
  );
}

위의 코드로, FancyInput에 전달된 ref 객체의 current 프로퍼티를 콘솔에 출력하면, undefined가 나온다. 이는 어찌된 일일까? myRef를 만들 때, useRef()에 어떠한 argument도 넘기지 않았기 때문에, myRef.current는 undefined 값을 갖는다. 그리고, 함수형 컴포넌트에 forwardRef를 통해 myRef 전달한 것에 불과하기 때문에, myRef.current의 값이 변경되지 않는다.

그러면 왜 사용할까?
하위 함수형 컴포넌트가 반환하는 리액트 요소가 'DOM 노드를 설명'한다면, DOM 노드에 ref를 줘서 해당 DOM 노드에 직접 접근할 수 있기 때문이다.

마무리

ref를 통해, 함수형 컴포넌트에 전달하고, DOM 노드에 접근하는 방법을 살펴봤다. ref의 다른 사용 사례는, 추후에 살펴보자.

0개의 댓글