React | 7. ref: DOM에 이름 달기

sojung·2021년 2월 13일
0

React

목록 보기
7/8
post-thumbnail

- HTML에서 id를 사용하여 DOM에 이름을 다는 것처럼 리액트 프로젝트 내부에서 DOM에 이름을 다는 방법이 있다. → ref(reference)

리액트 컴포넌트 안에서 id를 사용 못하는가?
사용할 수는 있지만, 같은 컴포넌트를 여러 번 사용한다고 가성하면, HTML에서 DOM의 id는 유일해야하는데, 이런 상황에서는 중복 id를 가진 DOM이 여러 개 생긴다. → 잘못된 사용! ref는 전역적으로 작동하지 않고 컴포넌트 내부에서만 작동하기 때문에 이런 문제가 생기지 않는다.

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

특정 DOM에 작업을 해야 할 때 ref를 사용한다. → 구체적으로 DOM을 꼭 직접적으로 건드려야 할 때 사용한다.

1. 예제 컴포넌트 생성

/* ValidationSample.css */

.success {
  background-color: lightgreen;
}

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

import React, { Component } from "react";
import "../css/ValidationSample.css"; //css를 import한다.

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",
    });
  };
  render() {
    return (
      <div>
        <input
          type="password"
          value={this.state.password}
          onChange={this.handleChange}
          className={
            this.state.clicked
              ? this.state.validated
                ? "success"
                : "failure"
              : ""
          }
        ></input>
        <button onClick={this.handleButtonClick}>검증하기</button>
      </div>
    );
  }
}

export default ValidationSample;
  • input : onChange 이벤트 발생 → handleChange를 호출하여 state의 password 값을 업데이트 한다.
  • button : onClick 이벤트가 발생 → handleButtonClick을 호출하여 clicked 값을 참으로 설정, validated 값을 검증 결과로 설정하였다.
  • input의 className값은 버튼을 누르기 전에는 비어 있는 문자열을 전달하며, 버튼을 누른 후에는 검증 결과에 따라 success 값 또는 failure 값을 설정한다.

2. DOM을 꼭 사용해야 하는 상황

state를 사용하여 우리에게 필요한 기능을 구현했지만, 가끔 state만으로 해결할 수 없는 기능이 있다.

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

이때는 어쩔 수 없이 DOM에 직접적으로 접근해야 하는데, 이를 위해 바로 ref를 사용한다.


ref 사용

ref 사용하는 방법은 두 가지이다.

1. 콜백 함수를 통한 ref 설정

ref를 만드는 가장 기본적인 방법은 콜백 함수를 사용하는 것이다. ref를 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달해준다. 이 콜백 함수는 ref 값을 파라미터로 전달받는다. 그리고 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정한다.

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

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

2. createRef를 통한 ref 설정

ref를 만드는 또 다른 방법은 리액트에 내장되어 있는 createRef라는 함수를 사용하는 것이다.

//RefSample.js

import React, { Component } from "react";

class RefSample extends Component {
  input = React.createRef(); // 1. createRef 함수 사용, 멤버 변수로 React.crateRef()를 담아 줌

  handleFocus = () => {
    this.input.current.focus(); // 3. ref를 설정해 준 DOM에 접근
  };
  render() {
    return (
      <div>
        <input ref={this.input}></input> // 2. 멤버 변수를 ref props로 넣어 줌
      </div>
    );
  }
}

export default RefSample;
  • createRef를 사용하여 ref를 만들려면 우선 컴포넌트 내부에서 멤버 변수로 React.createRef()를 담아준다.
  • 해당 멤버 변수를 ref를 달고자 하는 요소에 ref props로 넣어주면 ref 설정이 완료된다.
  • 설정한 뒤 나중에 ref를 설정해 준 DOM에 접근하려면 this.input.current를 조회하면 된다. 콜백 함수를 사용할 때와 다른 점은 뒷부분에 .current를 넣어주어야 한다.

3. 적용

ValidationSample 컴포넌트는

  • input 요소를 클릭하면 포커스가 되면서 텍스트 커서가 깜박인다.
  • 버튼을 누르면 포커스가 버튼으로 넘어가면서 왼쪽 input 요소의 텍스트 커서가 더 이상 보이지 않는다.
  • 콜백 함수를 이용하여 → 버튼을 한 번 눌렀을 때, 포커스가 다시 input 쪽으로 자동으로 넘어가도록 코드를 작성해보자!
import React, { Component } from "react";
import "../css/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"
              : ""
          }
        ></input>
        <button onClick={this.handleButtonClick}>검증하기</button>
      </div>
    );
  }
}

export default ValidationSample;
  • input에 ref 달기
  • 버튼 onClick 이벤트 코드 수정

컴포넌트에 ref 달기

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

1. 사용법

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

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

  • 스크롤바를 아래로 내리는 작업을 부모 컴포넌트에서 실행.

    스크롤 박스가 있는 컴포넌트를 하나 만들고 → 컴포넌트에 ref 달고 → ref를 이용하여 컴포넌트 내부 메서드 호출


2. 컴포넌트 파일 생성

컴포넌트 파일 생성

//ScollBox.js

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: "650%",
      background: "linear-gradient(white, black)",
    };
    return (
      <div
        style={style}
        ref={(ref) => {
          this.box = ref;
        }}
      >
        <div style={innerStyle} />
      </div>
    );
  }
}

export default ScrollBox;
  • JSX의 인라인 스타일링 문법으로 스크롤 박스를 만든다.
  • 최상위 DOM에 ref를 달아준다.

3. 컴포넌트에 메서드 생성

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

  • scrollTop : 세로 스크롤바 위치(0~350)
  • scrollHeight : 스크롤이 있는 박스 안의 div 높이(650)
  • clientHeight : 스크롤이 있는 박스의 높이(300)
import React, { Component } from "react";

class ScrollBox extends Component {
  scrollToBottom = () => { // 스크롤 바를 아래로 내리기 위한 코드
    const { scrollHeight, clientHeight } = this.box; // 비구조화 할당 문법 사용
    // const scrollHeight = this.box.scrollHeight;
    // const clientHeight = this.box.cliengHeight;
    this.box.scrollTop = scrollHeight - clientHeight;
  };
  render() {
    const style = {
      border: "1px solid black",
      height: "300px",
      width: "300px",
      overflow: "auto",
      position: "relative",
    };

    const innerStyle = {
      width: "100%",
      height: "650%",
      background: "linear-gradient(white, black)",
    };
    return (
      <div
        style={style}
        ref={(ref) => {
          this.box = ref;
        }}
      >
        <div style={innerStyle} />
      </div>
    );
  }
}

export default ScrollBox;

스크롤바를 맨 아래쪽으로 내리려먼 scrollHeight 에서 clientHeight 높이를 빼면 된다.

4. 컴포넌트에 ref 달고 내부 메서드 사용

App 컴포넌트에서 ScrollBox에 ref를 달고 버튼을 만들어 누르면, ScrollBox 컴포넌트의 scrollToBottom 메서드를 실행하도록 코드를 만든다.

//App.js

import React, { Component } from "react";
import "./App.css";
import ScrollBox from "./components/ScrollBox";

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

export default App;

App.js 코드는 함수형 컴포넌트로 바꾸면 에러가 난다. 이유를 찾아보니 함수형 컴포넌트에서는 ref를 제한적으로 사용할 수 있다. 아래의 공식문서 확인하기.
https://reactjs-kr.firebaseapp.com/docs/refs-and-the-dom.html


정리

  • 컴포넌트 내부에서 DOM에 직접 접근해야 할 때는 ref를 사용하지만, 먼저 ref를 사용하지 않고도 원하는 기능을 구현할 수 있는지 반드시 고려한 후에 활용한다.
  • 서로 다른 컴포넌트끼리 데이터를 교류할 때 ref를 사용하는 것이 아님! 물론 할 수는 있지만 컴포넌트에 ref를 달고 그 ref를 다른 컴포넌트로 전달하고, ... 다른 컴포넌트에서 ref로 전달받은 컴포넌트의 메서드를 실행하고... → 복잡해서 유지 보수가 불가능하다. 컴포넌트끼리 데이터를 교류할 때는 언제나 데이터를 부모 ↔ 자식 흐름으로 교류해야 한다. 나중에 리덕스 혹은 Context API를 사용하여 효율적으로 교류하는 것을 배울 예정.
  • 함수형 컴포넌트에서 ref를 사용하는 것은 배우지 않았다. 함수형 컴포넌트에서는 useRef라는 Hook 함수를 사용한다. React.createRef와 유사하다.
profile
걸음마코더

0개의 댓글