[React] ref: DOM에 이름 달기

🌊·2021년 12월 8일
0

React

목록 보기
5/20

일반 HTML에서 DOM 요소에 이름을 달 때는 id를 사용한다.

요소에 id를 달면 css에서 특정 스타일을 적용하거나 자바스크립트로 해당 요소에 작업을 할 수 있다.

ref (reference)

HTML에서 id를 사용하여 DOM에 이름을 다는 것처럼 리액트 프로젝트 내부에서 DOM에 이름을 다는 방법

리액트 컴포넌트에 id를 사용하는 것은 권장하지 않는다.
컴포넌트는 여러 번 사용할 수 있는데 id는 유일한 값이기 때문에 잘못된 사용이 될 수 있다.
ref는 컴포넌트 내부에서만 작동하기 떄문에 문제가 발생하지 않는다.

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

DOM을 직접 건드려야 할 때 사용한다.
함수 컴포넌트에서 ref를 사용하려면 Hooks를 사용해야한다.

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",
    });
    console.log(this.state);
  };

  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;

handleChange 메서드를 통해서 state의 password 값을 업데이트 한다.
handleButtonClick 메서드를 통해서 clicked의 값을 true로 변경하고 validated의 값을 업데이트 한다.
input의 className이 변경됐을 때 css에서 색상을 변경시킨다.

DOM을 꼭 사용해야 하는 상황

state를 사용하여 필요한 기능을 구현하지만 state만으로 해결할 수 없는 기능이 있다.

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

이때는 DOM에 직접적으로 접근하기 위해서 ref를 사용한다.

ref 사용

사용방법 1) 콜백 함수를 통한 ref 설정

  • 가장 기본적인 방법

ref를 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달해주면 된다.
이 콜백 함수는 ref 값을 파라미터로 전달 받으며 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수(this.input)로 설정해준다.

콜백 함수 사용 예시

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

사용방법 2) 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;

DOM 요소에 ref 적용하기

ValidationSample.js

handleButtonClick = () => {
  this.setState({
    clicked: true,
    validated: this.state.password === "0000",
  });
  this.input.focus();
};

<input
  ref={(ref) => {
    this.input = ref;
  }}
  type="password"
  value={this.state.password}
  onChange={this.handleChange}
  className={this.state.clicked ? (this.state.validated ? "success" : "failure") : ""}
/>

button을 누르게 되면 포커스가 button 요소를 가리키고 있다.
ref를 통해서 button을 눌렀을 때 포커스를 input 요소를 향할 수 있게 한다.

컴포넌트에 ref 적용하기

주로 컴포넌트 내부에 있는 DOM을 컴포넌트 외부에서 사용할 때 사용한다.

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

MyComponent 내부의 메서드 및 멤버 변수에도 접근할 수 있다.
내부의 ref에도 접근 가능 (myComponent.handleClick, myComponent.input 등)

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 (
      // 컴포넌트에 ref 달기
      <div
        style={style}
        ref={(ref) => {
          this.box = ref;
        }}
      >
        <div style={innerStyle}></div>
      </div>
    );
  }
}

export default ScrollBox;

return 되는 div(ScrollBox Component)에 ref를 달았다.
해당 ref를 통해서 컴포넌트 내부 메서드를 호출할 수 있다.

ref 이용해서 컴포넌트 내부 메서드 호출하기

컴포넌트 내부 메서드 작성

ScrollBox.js

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

ES6의 비구조화 할당 문법을 사용했다.

App.js

import React, { Component } from "react";
import ScrollBox from "./ref/ScrollBox";

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

export default App;

전체 흐름

버튼을 클릭해서 ScrollBox 컴포넌트 내부를 조작하기 위해서 ScrollBox 컴포넌트에 ref를 적용했다.
scrollToBottom 메서드가 실행되면 this.box의 scrollTop을 업데이트하게 되는데, this.box는 DOM 요소에 적용된 ref를 통해서 해당 요소를 찾을 수 있다.

문법상으로 onClick = {this.scrollBox.scrollBottom} 같은 형식으로 작성해도 틀린 것은 아니다.
하지만 컴포넌트가 처음 렌더링 될 때 this.scrollBox 값이 undefined이므로 위의 값을 읽어오는 과정에서 오류가 발생할 수 있다.
화살표 함수를 통해서 새로운 함수를 만들기 때문에 버튼을 클릭했을 때 this.scrollBox.scrollBottom 값을 읽어와서 실행하므로 오류가 발생하지 않는다.

정리

컴포넌트끼리 데이터를 교류할 때는 언제나 데이터를 부모 <-> 자식 흐름으로 교류해야 한다.
서로 다른 컴포넌트끼리 데이터를 교류할 때 ref를 사용하는 것은 잘못 사용된 것이다. (스파게티처럼 구조가 꼬여서 유지보수가 불가능해진다.)
리덕스 혹은 Context API를 사용해서 효율적으로 교류할 수 있다.
함수 컴포넌트에서 ref를 사용하기 위해서는 useRef라는 Hook 함수를 사용한다.

0개의 댓글