[ react ] Ref : DOM에 이름 달기

26540 단어 ref리액트ReactReact

ref란?

특정 DOM 요소에 어떤 작업을 해야할 때 해당 Element에 id를 달면 css에서 특정 id에 특정 스타일을 적용하거나 자바스크립트에서 해당 id를 가진 element를 찾아 작업을 할 수 있다.

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

🤔 그럼 리액트 컴포넌트에는 id를 사용하면 안될까?

  • 사용할 수 있다. JSX안에서 DOM에 id를 달면 해당 DOM을 렌더링할 때 그대로 전달된다. 하지만 특수한 경우가 아니면 사용을 권장하지 않는다. 예를 들어 같은 컴포넌트를 여러 번 사용한다고 가정해봤을 때 HTML에서 id는 유일해야하는데(Unique), 이런 상황에서 중복 id를 가진 DOM이 여러개 생성될 수 있다.
  • ref는 전역적으로 작동하지 않고 컴포넌트 내부에서만 작동하기 때문에 이런 문제가 생기지 않는다. id를 함께 사용해야하는 상황이라면 컴포넌트를 만들때마다 id 뒷부분에 추가 텍스트를 붙여서 중복 id가 발생하는 것을 방지해야한다. (ex. button01, button02...)

ref는 'DOM을 꼭 직접적으로 건드려야 할 때' 사용한다.

ref 사용을 위해 우선 예제로 ValidationInput이라는 클래스 컴포넌트를 작성한다. 함수형 컴포넌트의 경우 useRef라는 Hook을 이용하기 때문에 useRef 포스트를 참고하자.

🧩 validationInput.css

.sucess {
  background-color: mediumaquamarine;
}
.failure {
  background-color: palevioletred;
}

🧩 validationInput_ref.js

import { Component } from "react";
import "./ValidationInput.css";

class ValidationInput extends Component {
  state = {
    password: "",
    clicked: false,
    validate: false,
  };
  handleChange = (e) => {
    this.setState({
      password: e.target.value,
    });
  };
  handleClick = () => {
    this.setState({
      clicked: true,
      validate: this.state.password === "0000",
    });
  };

  render() {
    return (
      <div>
        <input
          type="password"
          value={this.state.password}
          onChange={this.handleChange}
          className={
            this.state.clicked
              ? this.state.validate
                ? "sucess"
                : "failure"
              : ""
          }
        />
        <button onClick={this.handleClick}>검증하기</button>
      </div>
    );
  }
}

export default ValidationInput;

🧩 App.js

class App extends Component {
  render() {
    return <ValidationInput />;
  }
}

위 예제에서는 state를 사용하여 필요한 기능을 구현했지만, state만으로 해결할 수 없는 기능들이 있다.

DOM을 꼭 사용해야 하는 상황 == ref를 사용해야하는 상황

  • 특정 input 포커스 주기
  • 스크롤 박스 컨트롤
  • canvas 요소에 그림 그리기 등

이제 예제를 기반으로 ref를 사용해보자.

ref 사용

callback 함수를 통한 ref 설정

ref를 만드는 가장 기본적인 방법은 콜백 함수를 사용하는 것이다. ref를 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달 해준다. 콜백 함수는 ref 값을 파라미터로 전달 받는다. 그리고 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정해준다.
<input ref={(ref)=> {this.input=ref}}
이렇게 하면 this.input은 input 요소의 DOM을 가리킨다. ref 이름은 자유롭게 지을 수 있다. DOM 타입과 관계 없이 this.pumpkin =ref와 같이 말이다.

createRef를 통한 ref 설정

ref를 만드는 또 다른 방법은 리액트에 내장되어 있는 createRef라는 함수를 사용하는 것이다. ( 이 기능은 리액트 버전 16.3부터 도입되어 이전 버전은 작동하지 않는다. )

import { Component, createRef } from "react";

class RefSample extends Component {
  input = createRef();

  handleFocus = () => {
    this.input.current.focus();
  };
  render() {
    return (
      <div>
        <input ref={this.input} />
      </div>
    );
  }
}

export default RefSample;

createRef를 사용하여 ref를 만들려면 컴포넌트 내부에서 멤버 변수로 React.createRef()를 담아 주어야 한다.
input = createRef()
그리고 해당 멤버 변수를 ref를 달고자 하는 요소에 ref props로 넣어주면 ref 설정이 완료된다.

ref를 설정해 준 DOM에 접근하려면 this.input.current를 조회하면 된다. .current를 통해 조회하는 것이 콜백 함수를 사용하는 방식과 다른점이다ㅏ.

이제 '검증하기' 버튼을 누르면 input에 포커스를 주도록 ref를 추가해보자.
🧩 validationInput_ref.js

import { Component } from "react";
import "./ValidationInput.css";

class ValidationInput extends Component {
 	...
    
    this.setState({
      clicked: true,
      validate: this.state.password === "0000",
    });
    // input에 포커스 전달
    this.input.focus();
  };

  render() {
    return (
      <div>
        <input
          type="password"
          value={this.state.password}
          onChange={this.handleChange}
          className={
            this.state.clicked
              ? this.state.validate
                ? "sucess"
                : "failure"
              : ""
          }
		// ref 추가 	
          ref={(ref) => {
            this.input = ref;
          }}
        />
        <button onClick={this.handleClick}>검증하기</button>
      </div>
    );
  }
}

export default ValidationInput;

'검증하기' 버튼을 클릭했을 때 input에 포커스가 생기는 것을 확인할 수 있다.

컴포넌트에 ref 달기

리액트 컴포넌트에도 ref를 달 수 있다. 이 방법은 주로 컴포넌트 내부에 있는 DOM을 컴포넌트 외부에서 사용할 때 쓴다. 사용법은 DOM에서 ref를 다루는 것과 같다.
<MyComponent ref={(ref)=> {this.myComponent=ref}}/>
이렇게 하면 MyComponent 내부의 메서드 및 멤버 변수에도 접근할 수 있다. 즉, 내부 ref에도 접근할 수 있다.
(ex. myComponent.handleClick , myComponent.button )

이번 예제는 스크롤 박스가 있는 컴포넌트를 만들고, 스크롤바를 아래로 내리는 작업을 부모 컴포넌트에서 컨트롤해보자.

  • scrollTop : 세로 스크롤바 위치 (0~350)
  • scrollHeight : 스크롤이 있는 박스 안의 그래디언트 div 높이 (650)
  • clientHeight : 스크롤이 있는 박스 높이 (300)

🧩 ScrollBox.js

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() {
    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;

🧩 App.js

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

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

export default App;

💥 '맨 밑으로' 버튼의 클릭 이벤트 문법상 onClick = {this.scrollBox.scrollBottom} 같은 형식도 틀리지 않은 문법이다. 하지만 컴포넌트가 처음 렌더링될 때는 this.scrollBox 값이 undefined 이므로 this.scrollBox.scrollBottom 값을 읽어 오는 과정에서 오류가 발생한다. 화살표 함수를 사용하여 아예 새로운 함수를 만들고 그 내부에서 this.scrollBox.scrollBottom 메서드를 실행하면, 버튼을 누를 때 메서드 값을 읽어와서 실행하므로 오류를 발생시키지 않는다.

좋은 웹페이지 즐겨찾기