4. ref: DOM에 이름 달기

나혜수·2023년 3월 14일
0

리액트

목록 보기
4/23

HTML에서 DOM 요소에 이름을 달 때는 id를 사용한다. 이렇게 하면 특정한 id에 해당하는 DOM 요소에만 스타일을 따로 적용하거나, 자바스크립트에서 해당 DOM 요소에 접근하여 여러 가지 작업을 할 수 있다. 이처럼 리액트에서도 DOM을 선택해 직접 접근하기 위해 ref를 사용한다.

일반적으로 부모 컴포넌트가 자식 컴포넌트와 상호작용 가능한 것은 props가 유일하다. 자식 컴포넌트를 수정하려면 새로운 props를 전달하여 자식을 렌더링 해야하는데, 가끔 직접적으로 자식을 수정해야하는 경우도 발생한다. 이때 DOM element에 접근하기 위해 ref를 사용한다.

❗바닐라 자바스크립트의 DOM 접근 방식인 getElementById 등을 사용하면 리액트의 메소드를 쓸 수 없다!

🧐 리액트 컴포넌트 안에서는 id를 사용하면 안되나?
리액트 컴포넌트 안에서도 id를 사용할 수는 있다. JSX 안에서 DOM에 id를 달면 해당 DOM을 렌더링할 때 그대로 전달된다. 하지만 특수한 경우가 아니면 사용을 권장하지 않는다. 예를 들어 같은 컴포넌트를 여러 번 사용한다고 하자. id는 고유해야 하는데 이런 상황에서는 중복 id를 가진 DOM이 여러 개 생기니 잘못된 사용이다.
ref는 전역적이지 않고 컴포넌트 내부에서만 작동하기 때문에 이런 문제가 생기지 않는다.


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

ref는 DOM을 꼭 직접적으로 건드려야 할 때 사용한다. 예를 들어 바닐라 자바스크립트로 만든 웹 사이트에서 input을 검증할 때는 다음과 같이 특정 id를 가진 input에 클래스를 설정해준다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Example</title>
  
  <style>
    .success {
      background-color: lightgreen;
    }

    .failure {
      background-color: lightcoral;
    }
  </style>
  
  <script>
    function validate() {
      // DOM에 직접 접근 
      var input = document.getElementById('password');
    
      input.className='';
      if(input.value==='0000') {
        input.className='success';
      } else {
        input.className='failure';
      }
    }
  </script>
</head>
  
<body>
  <input type="password" id="password"></input>
  <button onclick="validate()">Validate</button>
</body>
</html>

리액트에서는 이런 작업을 굳이 DOM에 직접 접근하지 않아도 state로 구현할 수 있다.

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'
        })
    }


    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를 사용해 필요한 기능을 구현했지만, 가끔 state만으로 해결할 수 없는 기능이 있다.

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

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


ref 사용

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

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

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

방법2. createRef를 통한 ref 설정

리액트에 내장되어 있는 createRef 라는 함수를 사용해 ref를 만들 수도 있다. 이 기능은 리액트 v16.3부터 도입되었다. 콜백함수를 사용할 때와 다른 점은 DOM에 접근할 때 뒷부분에 .current를 넣어줘야 한다는 것이다.

import React, {Component} from 'react';

class RefSample extends Component {
    // 컴포넌트 내부에서 멤버변수로 React.createRef를 담아준다. 
    input = React.createRef();

    handleFocus = () => {
        this.input.current.focus();
        // DOM에 접근하려면 this.input.current 조회 
    };

    render() {
        return (
            <div>
                // 해당 멤버변수를 ref를 달고자 하는 요소에 ref props로 넣어준다. 
                <input ref={this.input} />
            </div>
        );
    }
}

export default RefSample;

예시

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}}
                    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

컴포넌트에 ref 달기

ScrollBox.js

import React, {Component} from "react";

class ScrollBox extends Component{
    scrollToBottom = () => {
        const {scrollHeight, clientHeight} = this.box // div 태그
        this.box.scrollTop =  scrollHeight - clientHeight
    }

    render(){
        const style = {
            border: '1px solid black',
            height: '300px',
            width: '300px',
            overflow: 'auto',
            position: 'relative'

        }

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

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

    }

}

export default ScrollBox

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;

profile
오늘도 신나개 🐶

0개의 댓글