상세 컨텐츠

본문 제목

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

21-22/21-22 리액트 마스터

by 도리에몽 2021. 11. 1. 19:10

본문

728x90

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

 

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

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

-> 'DOM'을 꼭 직접적으로 건드려야 할 때

1) 예제 컴포넌트 생성

클래스형 컴포넌트에서 ref를 사용하는 방법을 알아보자!

> 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({ //state의 password 값 업데이트
            password: e.target.value
        });
    }

    handleButtonClick = () => {
        this.setState({
            clicked: true, //clicke값 참으로 설정
            validated: this.state.password === '0000' //validated 값 검증
        })
    }

    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;
  • input의 className 값은 버튼을 누르기 전에 비어있는 문자열을 전달한다.
  • 버튼을 누른 후에는 검증 결과에 따라 success 값 또는 failure 값을 설정하여 이 값에 따라 input 색상이 초록색 또는 빨간색으로 나타나게 됨.

> App.js

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

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

export default App;

검증되었을 때 모습(0000)와 검증되지 않았을 때 모습(1111)

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

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

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

위와 같은 경우에는 어쩔 수 없이 DOM에 직접적으로 접근해야 하는데, 이를 위해 ref를 사용한다.

 

2. ref 사용

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

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

ref를 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달해 주면 된다.

이 콜백 함수는 ref값을 파라미터로 전달받고,

함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정해준다.

<input ref={(ref) => {this.input=ref}} />
  • 이렇게 하면 앞으로 this.input은 input 요소의 DOM을 가리키게 된다.

 

2) createRef를 통한 ref 설정

  • createRef는 리액트에 내장되어 있는 함수이다.
  • createRef 함수를 통해 ref를 만들면 더 적은 코드로 쉽게 만들 수 있다.
  • 리액트 v16.3부터 도입되었으며 이전 버전에서는 작동하지 않는다.
import React, { Component } from 'react';

class RefSaple extends Component {
	input = React.createRef(); //1
	
	handleFocus = () => {
		this.input.current.focus(); //ref를 설정해준 DOM에 접근
	} 

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

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

 

3) ValidationSample 코드 수정

첫 번째 방법이었던 콜백 함수를 통한 ref설정을 이용해 앞의 예제에 버튼을 누르면 포커스가 다시 input 쪽으로 자동으로 넘어가는 코드를 작성해보자!

> input에 ref달기

ValidationSample 컴포넌트에 ref를 달아보자

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

> 버튼 onClick 이벤트 코드 수정

    handleButtonClick = () => {
        this.setState({
            clicked: true,
            validated: this.state.password === '0000'
        });
        this.input.focus();
    }
  • this.input이 컴포넌트 내부의 input 요소를 가리키고 있으니, 일반 DOM을 다루듯이 코드를 작성하면 된다.

> 완성 코드

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

    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;

버튼을 누르면 바로 input으로 커서가 넘어간다.

 

3. 컴포넌트에 ref달기

  • 리액트에서는 컴포넌트에도 ref를 달 수 있다.
  • 이 방법은 컴포넌트 내부에 있는 DOM을 컴포넌트 외부에서 사용할 때 주로 사용한다.
  • 사용법
<My Component
		ref={(ref)=>{this.myComponent=ref}}
/>

이렇게 하면 myComponent 내부의 메서드 및 멤버 변수에도 접근할 수 있다.

(즉, 내부의 ref에도 접근할 수 있다.)

1) 부모 컴포넌트에서 스크롤바 내리기 실습

1. ScrollBox 컴포넌트 파일 만들기

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;

2. App 컴포넌트에서 스크롤 박스 컴포넌트 렌더링

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

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

export default App;

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.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;

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

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.scrollToBottom 값을 읽어 오는 과정에서 오류가 발생한다.

화살표 함수 문법을 사용하여 아예 새로운 함수를 만들고 그 내부에서 this.scrollBox.scrollToBottom 메서드를 실행하면, 버튼을 누를 때 (이미 한 번 렌더링을 해서 this.scrollBox를 설정한 시점) tis.scrollBox.scrollToBottom 값을 읽어 와서 실행하므로 오류가 발생하지 않는다.

Quiz

  1. HTML에서 id를 사용해 DOM에 이름을 다는 것처럼 리액트 프로젝트 내부에서 DOM에 이름을 다는 방법으로 사용하는 개념은?
  2. ref를 만드는 방법엔 2가지가 있는 리액트에 내장되어있는 함수를 사용하는 방법에 쓰이는 이 함수의 이름은?
더보기
더보기
더보기

1번 정답 ref
2번 정답 createRef

728x90

관련글 더보기