개발/React

[리액트를 다루는 기술] ref: DOM에 이름 달기

hayo 2023. 2. 20. 00:21

ref (reference) 란?

  • HTML 에서 id를 사용하여 DOM에 이름을 다는 것 처럼 리액트 프로젝트 내부에서 DOM에 이름을 다는 방법
  • 리액트 컴포넌트 안에서도 id를 사용할 수 있긴 하지만, id를 달아놓은 컴포넌트를 여러 번 사용할 경우 중복 id를 가진 DOM이 여러 개가 생기게 되므로 잘못된 사용이다!
  • 반면에, ref는 전역적으로 작동하지 않고 컴포넌트 내부에서만 작동하기 때문에 위의 id와 같은 문제가 발생하지 않는다.

 

1. ref는 어떤 상황에서 사용해야 할까? → DOM을 꼭! 직접적으로 건드려야 할 때

  • state를 사용하여 많은 기능을 구현할 수 있지만, state만으로 해결할 수 없는 기능들을 구현할 때
    === DOM에 직접적으로 접근해야 할 때, ref를 사용한다.
    • 특정 input에 포커스 주기
    • 스크롤 박스 조작하기
    • Canvas 요소에 그림 그리기 등

2. ref 사용

  • 콜백 함수를 통한 ref 설정
    • ref를 만드는 가장 기본적인 방법
    • ref를 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달
// this.input은 input 요소의 DOM을 가르킨다
<input ref={(ref) => {this.input=ref}} />
  • createRef를 통한 ref 설정
    • 리액트에 내장되어 있는 createRef라는 함수를 사용하여 ref를 만들면 더 적은 코드로 쉽게 사용할 수 있다.
      (v16.3부터 사용가능)
    • 이 함수를 사용하여 ref를 만들기 위해서는 먼저 컴포넌트 내부에서 멤버 변수에 React.createRef()를 할당해 주어야 한다.
      그리고 해당 변수를 ref를 달고자 하는 요소에 ref props로 넣어주면 ref 설정 완료!
import { Component } from 'react';

class RefSample extends Component {
  input = React.createRef();
    
  handleFocus = () => {
    this.input.current.focus();
  }
    
  render() {
    return (
      <div>
        <input ref={this.input} />
      </div>
    );
  }
}

export default RefSample;

3. 컴포넌트에 ref 달기

  • 주로 컴포넌트 내부에 있는 DOM을 컴포넌트 외부에서 사용할 때, 컴포넌트에 ref를 달아서 사용한다.
  • 컴포넌트에 ref를 다는 방법은 DOM에 ref를 다는 방법과 동일하다.
import { Component } from 'react';

class ScrollBox extends Component {

  scrollToBottom = () => {
    const { scrollHeight, clientHeight } = this.box;
    /*
    const scrollHeight = this.box.scrollHeight;
    const clientHeight = this.box.clientHeight;
    */
    
    this.box.scrollTop = scrollHeight - clientHeight;
  }
  
  render() { (...) }
}

export default SccrollBox;
import { Component } from 'react';
import ScrollBox from './ScrollBox';

class App extends Component {
  render() {
    return (
      <div>
        <ScrollBox ref={(ref) => this.scrollBox=ref} />
        <button onClick={() => this.scrollBox.scrollToBottom()}>
          맨 밑으로
        </button>
      </div>
    )
  }
}

export default App;

** 주의 사항 **
→ 문법상으로는 onClick = { this.scrollBox.scrollToBottom } 형식으로 작성해도 틀린 것은 아니지만,
    컴포넌트가 처음 렌더링 될 때 this.scrollBox 값이 undefined 이므로 해당 값을 읽어 오는 과정에서 오류가 발생한다!
화살표 함수 문법을 사용하여 아예 새로운 함수를 만들고 그 내부에서 this.scrollBox.scrollToBottom 메서드를 실행하면, 이미 한 번 렌더링을 해서 this.scrollBox가 설정이 되어있기 때문에 버튼을 누를 때 해당 메서드의 값을 정상적으로 읽어와서 실행한다.

 

4. 정리

  • 컴포넌트 내부에서 DOM에 직.접. 접근해야 할 때 ref를 사용한다.
  • BUT, 먼저 ref를 사용하지 않고도 원하는 기능을 구현할 수 있는지 반드시 고려한 후에 활용해야 한다.
  • 컴포넌트끼리 데이터를 교류할 때는 언제나 부모 ↔ 자식 흐름으로 교류해야 한다!
    너무 많은 컴포넌트들 간의 교류는 리액트 사상에 어긋난 설계!! DON'T DO IT!

 


참고자료

  • 리액트를 다루는 기술 [김민준 저]