ref

December 31, 2020

ref

일반적인 HTML 페이지에서는 아래처럼 id를 통해 각 요소에 접근할 수 있다.

<div id='my-id'>Hello World!</div>

물론 리액트에서도 이런 id를 사용할 수는 있으나 권장하지는 않는다. 리액트에서는 컴포넌트의 재사용으로 같은 컴포넌트가 여러 번 사용될 수 있는데 이러한 경우 DOM에서 id가 유일한 값을 가질 수 없게 된다.

리액트 프로젝트에서는 DOM에 이름을 다는 방법으로 ref를 이용한다. 그리고 이는 전역적인 것이 아니라 컴포넌트 내부에서만 동작하기 때문에 id가 중복되는 것과는 달리 유일하게 구분할 수 있다.

많은 케이스를 propsstate로 처리할 수 있다. 하지만 아래 상황같은 경우에는 DOM에 직접 접근하는 것이 필요하다.

  • 특정 input에 포커스 주기, 텍스트 선택 영역, 미디어 재생 등의 관리를 할 때
  • 스크롤 박스 조작할 때
  • Canvas 요소에 그림 그리기 등의 애니메이션을 직접적으로 실행할 때
  • 서드 파티 DOM 라이브러리를 리액트와 같이 사용할 때

리액트에서 ref를 사용하는 방법은 2가지가 있다. 아래는 ‘Focus the text input’라는 버튼을 클릭하면 텍스트 박스에 포커스가 발생하는 기능을 구현한 것이다.

  1. Callback 함수를 사용

ref를 달고자하는 엘리먼트에 콜백 함수를 전달한다. 이 함수는 해당 ref의 엘리먼트를 파라미터로 받으며 이를 내부 멤버 변수로 저장해두면 접근 가능하다.

import React, {Component} from 'react';

class CustomTextInput extends Component {
  textInput = null;
    
  focusTextInput = () => {
    this.textInput.focus();
  }
  
  render() {
    return (
    <div>
      <input
        type="text"
        ref={(ref) => {this.textInput = ref}} />
      <input
        type="button"
        value="Focus the text input"
        onClick={this.focusTextInput}
      />
    </div>
    );
  }
}

export default CustomTextInput;
  1. createRef를 사용

v16.3부터 도입되었으며 이전 버전에서는 동작하지 않는다. createRef를 통해 변수를 생성하고 엘리먼트의 ref props로 넣어주면 된다. Callback 함수와 다른 점은 엘리먼트 사용 시 .current를 넣어줘야 한다는 것이다.

import React, {Component} from 'react';

class CustomTextInput extends Component {
  textInput = React.createRef();
    
  focusTextInput = () => {
    this.textInput.current.focus();
  }
  
  render() {
    return (
    <div>
      <input
        type="text"
        ref={this.textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={this.focusTextInput}
      />
    </div>
    );
  }
}

export default CustomTextInput;

그리고 자주 있는 일은 아니지만 부모 컴포넌트에서 자식 컴포넌트 DOM으로 접근하는 일도 있을 수 있다. 자식 컴포넌트의 DOM에 접근하는 것은 컴포넌트의 캡슐화를 파괴하는 방식이기에 권장되지 않는다. 하지만 ref를 사용하는 이유와 같이 이에 관한 문법이 존재한다.

이는 컴포넌트에 ref를 다는 것이다. 그러면 자식 컴포넌트 내부의 메서드 및 멤버 변수 등에 접근 가능하며 ref에도 접근 가능해 진다. 아래는 버튼 클릭시 스크롤이 구현된 컴포넌트에서 스크롤을 가장 아래로 내리는 코드이다.

// App.js
import React, {Component} from 'react';
import './App.css';
import ScrollBox from './ScrollBox.js'

class App extends Component{
  scrollBox = React.createRef();
  
  render(){
    return (
      <div>
        <ScrollBox ref={this.scrollBox}/>
        {/* 
        onClick={this.scrollBox.current.scrollToBottom()는 오류가 발생한다.
        최초 렌더싱 시 scrollBox에 참조가 없으므로 scrollToBottom에 접근할 수 없음 
        */}
        <button onClick={() => this.scrollBox.current.scrollToBottom()}>
          맨 밑으로
        </button>
      </div>
    )
  };
}

export default App;
// ScrollBox.js
import React, {Component} from 'react';

class ScrollBox extends Component {

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

	render() {
		const outerStyle = {
			width: '300px',
			height: '300px',
			overflow: 'auto',
			position: 'relative'
		};

		const innerStyle = {
			width: '100%',
			height: '650px',
			backgroundColor: 'black'

		};

		return (
		<div
			style={outerStyle}
			ref={(ref) => {this.box = ref}}>
			<div style={innerStyle}></div>
		</div>
		);
	}
}

export default ScrollBox;

참고


songmk 🙁