다라다라V
article thumbnail
728x90
반응형

일반 HTML에서는 DOM 요소에 이름을 달 때 id 값을 사용합니다. 

<div id="dara-elemnt"></div>

 

특정 요소에 id 값을 달면 CSS에서 특정 id에 스타일을 적용하거나, 자바스크립트에서 해당 id를 찾아 작업하기도 합니다. 이렇게 HTML에서 id 를 사용하여 DOM에 이름을 다는 것처럼 리엑트 프로젝트 내부에서도 DOM에 이름을 다는 방법이 있습니다.

 

ref를 사용하지 않은 코드를 보며 ref의 필요성을 알아보고, ref를 사용한 코드를 작성해보겠습니다. 이후 컴포넌트에 직접 ref를 달아 사용하는 코드도 작성해보겠습니다.

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

📚 ref 의 주요 목적

예제를 통해 ref의 필요성을 알기 전에 REACT 공식 문서(https://ko.reactjs.org/docs/refs-and-the-dom.html)에서 소개하는 ref 에 대해 배워봅시다.

 

Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공합니다

 

 

📃 props 

  • 부모 컴포넌트가 자식과 상호작용하는 방법
  • 자식을 수정하기 위해서는 새로운 props를 매번 전달하여 새로 렌더링 해야함

📃 ref

  • 부모 컴포넌트에서 직접적으로 자식을 수정해야하는 경우도 존재
    ex) 부모 컴포넌트가 리액트 컴포넌트의 인스턴스나, DOM 엘리먼트를 직접 수정해야하는 경우
  • ref 를 console.log로 찍어보면 current 프로퍼티를 하나 가진 일반 객체
  • 리액트에서는 이 ref 객체를 통해  DOM에 직접적인 접근을 할 수 있음

📚 HTML 예제 확인 생성

다음과 같이 input을 이용하여 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() {
      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로 구현할 수 있을 것입니다. 함수형 컴포넌트에서 ref를 사용하려면 Hooks를 사용해야하기 때문에 이번 포스트에서는 클래스형 컴포넌트로 ref를 사용하도록 하겠습니다.


📚 예제 컴포넌트 생성

src 디렉터리 안에 다음과 같이 ValidationSample.css 과  ValidationSample.js 를 작성합니다.

/* ValidationSample.css */
.success {
    background-color: lightgreen;
}

.failure {
    background-color: lightcoral;
}
// ValidationSample.js
import { 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;
  • input에서는 onChange 이벤트가 발생하면 handleChange를 호출하여 state 의 password 값을 업데이트
  • button에서는 onClick 이벤트가 발생하면 handleButtonClick을 호출하여 clickedvalidated 값을 설정
  • input의 className 값은 삼항조건문을 이용하여 버튼을 누르기 전에는 빈 문자열을 전달, 버튼을 누른 뒤에는 검증 결과에 따라 success와 failure 값을 설정
  • className에서 설정된 값에 따라 input 색상을 변경

📚 App 컴포넌트에서 예제 컴포넌트 렌더링

  • App 컴포넌트에서 ValidationSample 컴포넌트를 불러와 렌더링함
  • App 컴포넌트에서도 ref를 사용할 예정이라 클래스형 컴포넌트로 작성하기를 권장함
// App.js
import { Component } from "react";
import ValidationSample from "./ValidationSample";

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

export default App;

📚 DOM 을 꼭 사용해야 하는 상황

앞선 예제처럼 state만으로 해결할 수 없는 기능도 존재합니다.

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

등 반드시 DOM 요소에 직접적으로 접근해야하는 경우가 존재합니다. 이때 ref를 사용합니다.


📌 ref 사용

ref를 사용하는 방법은 두 가지 입니다.

 

📚 콜백 함수를 통한 ref 설정

<input ref={(ref) => {this.input=ref}} />
  • 콜백 함수 : ref를 만드는 가장 기본적인 방법
  • ref를 달고자한 요소에 ref라는 콜백 함수를 props로 전달
  • ref를 파라미터로 전달받으며, 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정
  • 위의 예제에서 this.input은 input 요소의 DOM을 가리킴

 


📚 createRef를 통한 ref 설정

  • createRef : 리액트에 내장되어 있는 creatRef 함수를 이용하여 ref를 만드는 방법
  • 더 적은 코드로 쉽게 사용할 수 있음
import { 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;
  • creatRef를 사용하여 ref를 만들려면 우선 컴포넌트 내부에서 멤버 변수로 React.createRef( )를 담아줌
  • 해당 멤버 변수를 ref를 달고자 하는 요소에 ref props로 넣어주면 ref 설정 완료
  • 나중에 ref를 설정해준 DOM에 접근하려면 this.input.current를 조회
  • 콜백 함수와 다른 점은 .current를 넣어주여아함

 

이 포스트에서는 콜백 함수를 이용하여 ref를 다루어 볼 예정입니다.


📚 ref 적용

ValidationSample 컴포넌트의 렌더링 결과 창에서 input 요소를 클릭하면 포커스가 되면서 텍스트 커서가 깜빡입니다.

이번에는 버튼을 한 번 눌렀을 때, 자동으로 포커스가 다시 input 쪽으로 넘어가도록 코드를 작성해봅시다.

 

📃 input에 ref 달기

// ValidationSample.js의 input 요소

( ... )
<input
    ref={(ref) => this.input=ref}
    ( ... )
/>
( ... )
  • 콜백 함수를 이용하여 ValidationSample 컴포넌트에도 ref를 달기

📃 버튼 onClick 이벤트 코드 수정

// ValidationSample.js의 handleButtonClick 메서드

handleButtonClick =() => {
    this.setState({
        clicked: true, 
        validated: this.state.password === '0000'
    });
    this.input.focus(); //주목
}
  • 버튼에 onClick 이벤트가 발생할 때 input에 포커스를 주도록 코드를 수정
  • this.input 컴포넌트 내부의 input 요소를 가리키고 있으니, 일반 DOM을 다루듯이 코드를 작성할 수 있음

📌 컴포넌트에 ref 달기

리액트에서는 DOM에 ref를 다는 방법 그대로 컴포넌트에도 ref 달 수 있습니다. 

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

다음과 같이 코드를 작성하면 MyComponent 내부의 메서드 및 멤버 변수에도 접근하는 코드로, 내부 ref에도 접근할 수 있습니다. 

 

이번 챕터에서는 스크롤 박스가 있는 컴포넌트를 만들고, 스클롤바를 아래로 내리는 작업을 부모 컴포넌트에서 실행하는 코드를 작성해보겠습니다.


📚 컴포넌트 초기 설정

// ScrollBox.js

import { Component } from "react";

class ScrollBox extends Component {
    render() {
        const style = {
            border: '1px solid black' ,
            width: '300px',
            height: '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;
  • JSX의 인라인 스타일링 문법으로 ScrollBox라는 컴포넌트 파일을 만들기
// App.js

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

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

export default App;
  • App 컴포넌트에서 ScrollBox 컴포넌트 렌더링

 


📚 컴포넌트에 메서드 생성

 컴포넌트에 스크롤바를 맨 아래쪽으로 내리는 메서드 만들어 보겠습니다.

// 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() {
        ( ... )
    }
}

export default ScrollBox;
  • scrollTop : 세로 스클롤바 위치 (0~350)
  • scrollHeight : 스크롤이 있는 박스 안의 div 높이 (650)
  • clientHeight : 스크롤이 있는 박스의 높이 (300)
  • 맨 아래쪽으로 스크롤바를 내리고 싶다면 scrollHeight에서 clientHeight을 빼면됨

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

App 컴포넌트에서 ScrollBox에 ref를 달고 버튼을 만들어 누르면, ScrollBox 컴포넌트의 scrollBottom 메서드가 실행되도록 코드를 작성합시다.

// App.js

import { 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;

📌 정리

📚 정리

  • 컴포넌트 내부에서 DOM에 직접 접근해야할 때는 ref를 사용
  • 단, ref를 사용하지 않고 원하는 기능을 구현할 수 있는지 반드시 확인

 

📚 주의할 점

  • 서로 다른 컴포넌트끼리 데이터를 교류할 때 ref를 사용하지 말 것
  • 컴포넌트에 ref를 달고 그 ref를 다른 컴포넌트로 전달하다보면 구조가 꼬여 유지 보수가 불가능함
  • 이는 리액트 사상에 어긋난 설계
  • 컴포넌트끼리 데이터를 교류할 때는 반드시 데이터를 부모 ↔ 자식 흐름으로 교류해야함

 

📚 함수 컴포넌트

  • 함수 컴포넌트는 useRef 라는 Hook 함수를 사용
  • 사용법은 앞에서 배운 React.createRef와 유사함

 

 

 

 

반응형
profile

다라다라V

@DaraDaraV

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!