[React] 리액트를 다루는 기술 - 5장 ref: DOM에 이름 달기 정리

일반 HTML에서 DOM 요소에 이름을 달 때는 id를 사용한다. (<div id="my-element"></div>) 그렇다면 리액트에서는 어떨까? 리액트에서도 id를 사용할 수는 있으나, 컴포넌트를 여러 번 사용하는 것과 같은 경우에는 중복 id를 가진 DOM이 여러 개가 생기기 때문에 다른 방법을 이용해야 한다. 바로 컴포넌트 내부에서만 작동하는 ref(reference)이다.

ref는 어떤 상황에서 사용해야 할까?

refDOM을 꼭 직접적으로 건드려야 할 때 사용된다. 먼저 ref가 아닌 state를 사용해서 비밀번호 validation을 구현해 보자.

/* ValidationSample.css */

.success {
  background-color: lightgreen;
}
.failure {
  background-color: lightcoral;
}
// ValidationSample.js

import React, { Component } from 'react';
import './ValidationSample.css';

class ValidationSample extends Component {
  state = {
    password: '',
    clicked: false,
    validated: false
  }

  handleChange = (e) => {
    this.setState({
      password: e.target.value
    });
  }

  handleButtonClick = () => {
    this.setState({
      clicked: true,
      validated: this.state.password === '0000' // 비번이 맞으면 validated true
    })
  }

  render() {
    return (
      <div>
        <input
          type="password"
          value={this.state.password}
          onChange={this.handleChange}
          className={this.state.clicked ? (this.state.validated ? 'success' : 'failure') : ''}
        />  // 클릭했을 때 () 안 삼항연산자로 검증
        <button onClick={this.handleButtonClick}>검증하기</button>
      </div>
    );
  }
}

export default ValidationSample;

가끔은 이처럼 state만으로는 구현할 수 없는 기능들이 있다.

  • 특정 input에 포커스 주기
  • 스크롤 박스 조작하기
  • Canvas 요소에 그림 그리기 등

위와 같은 경우에는 어쩔 수 없이 DOM에 직접 접근해야 함.

ref 사용

콜백 함수를 통한 ref 설정

ref를 만드는 가장 기본적인 방법은 콜백 함수를 사용하는 것이다.

<input ref={(ref) => {this.input=ref}} />

이렇게 하면 앞으로 this.inputinput 요소의 DOM을 가리킨다. ref의 이름은 원하는 것으로 자유롭게 지정할 수 있다. DOM 타입과 관계없이 this.superman = ref처럼 마음대로 지정

createRef를 통한 ref 설정

리액트에 내장되어 있는 createRef 함수를 사용할 수도 있다.

import React, { 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;

createRef를 사용하여 ref를 만들려면 컴포넌트 내부에서 멤버 변수로 React.createRef()를 담아 주어야 한다. 그리고 해당 멤버 변수를 ref를 달고자 하는 요소에 ref props로 넣어 주면 ref 설정이 완료된다.
나중에 ref를 설정해 준 DOM에 접근하려면 this.input.current를 조회하면 된다. 콜백 함수를 사용할 때와 다른 점은 이처럼 뒤에 .current를 넣어 주어야 한다는 것이다. 사실 아직까진 잘 모르겠지만 적용할 때 어떻게 쓰이는지 알겠지?

적용

위에서 state를 사용했던 코드의 렌더링 결과를 살펴보면

input 요소를 클릭하면 포커스가 되면서 텍스트 커서가 깜빡이고,

button 요소를 클릭하면 포커스가 버튼으로 넘어가면서 input 요소의 텍스트 커서가 더 이상 보이지 않는다. 버튼을 한 번 눌렀을 때, 포커스가 다시 input 쪽으로 자동으로 넘어가도록 코드를 작성해 보자.

// ValidationSample.js

import React, { Component } from 'react';
import './ValidationSample.css';

class ValidationSample extends Component {
  state = {
    password: '',
    clicked: false,
    validated: false
  }

  handleChange = (e) => {
    this.setState({
      password: e.target.value
    });
  }

  handleButtonClick = () => {
    this.setState({
      clicked: true,
      validated: this.state.password === '0000'
    });
    this.input.focus(); // input에 포커스
  }

  render() {
    return (
      <div>
        <input
          ref={(ref) => this.input=ref} // 콜백 함수로 ref 설정
          type="password"
          value={this.state.password}
          onChange={this.handleChange}
          className={this.state.clicked ? (this.state.validated ? 'success' : 'failure') : ''}
        />
        <button onClick={this.handleButtonClick}>검증하기</button>
      </div>
    );
  }
}

export default ValidationSample;


버튼을 눌렀을 때 버튼이 아닌 input 쪽으로 focus가 자동으로 넘어간 것을 확인할 수 있다.

컴포넌트에 ref 달기

리액트에서는 컴포넌트에도 ref를 달 수 있다. 이 방법은 주로 컴포넌트 내부에 있는 DOM을 컴포넌트 외부에서 사용할 때 쓴다.

<MyComponent
  ref={(ref) => {this.myComponent=ref}}
/>

이렇게 하면 MyComponent 내부의 메소드 및 멤버 변수에도 접근할 수 있다. 즉, 내부의 ref에도 접근할 수 있다.
이번에는 스크롤 박스가 있는 컴포넌트를 하나 만들고, 스크롤바를 아래로 내리는 작업을 부모 컴포넌트에서 실행해 볼 것이다.

import React, { Component } from 'react';

class ScrollBox extends Component {
    render() {
        const style = {
            border: '1px solid black',
            height: '300px',
            width: '300px',
            overflow: 'auto',
            position: 'relative'
        };

        const innerStyle = {
            width: '100%',
            height: '650px',
            background: 'linear-gradient(white, black)'
        };

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

export default ScrollBox;

컴포넌트에 스크롤바를 맨 아래쪽으로 내리는 메소드를 만들어보자. 자바스크립트로 스크롤바를 내릴 때는 DOM 노드가 가진 다음 값들을 사용한다.

  • scrollTop: 세로 스크롤바 위치 (0~350) -> 350일 때 스크롤바 맨 아래
  • scrollHeight: 스크롤이 있는 박스 안의 div 높이 (650)
  • clientHeight: 스크롤이 있는 박스의 높이 (300)

ScrollBox.js의 컴포넌트 내부에 아래 메소드를 선언한다.

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

다음으로 App 컴포넌트에서 ScrollBoxref를 달고 버튼을 만들어 누르면, ScrollBox 컴포넌트의 scrollToBottom 메소드를 실행하도록 코드를 작성한다.

// App.js
import React, { 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.scrollBottom} 같은 형식으로 작성하는 것이 가능하다. 하지만 컴포넌트가 처음 렌더링될 때는 this.scrollBox 값이 undefined 이므로 화살표 함수로 아예 새로운 함수를 만들고 버튼을 누를 때 this.scrollBox.scrollBottom 값을 읽어 와서 실행하므로 오류가 발생하지 않는다. (이미 한 번 렌더링을 해서 this.scrollBox를 설정한 시점)

ScrollBox 컴포넌트의 scrollToBottom 메소드를 실행되어 스크롤바가 맨 아래로 이동한 것을 확인할 수 있다.

좋은 웹페이지 즐겨찾기