01.7 React ref (DOM에 이름 달기)

React.js

01.7 React ref

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

<!-- public/index.html 파일 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <meta name="theme-color" content="#000000" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div> <!-- id가 root인 div를 확인할 수 있다 -->
  </body>
</html>


// src/index.js 파일
(...)
// id가 root인 요소에 리액트 컴포넌트를 렌더링하는 코드
ReactDOM.render(<App />, document.getElementById('root'));
(...)

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

※ 리액트 컴포넌트 안에서 id를 사용하지 않는 이유
리액트 컴포넌트를 반복적으로 사용할 경우 유일(unique)해야하는 id 속성이 중복으로 사용되기 때문이다.
ref 사용할 때는 DOM을 직접적으로 핸들링할 때이다.
아래의 코드처럼 비밀번호의 유효성 검사를 할 때 클릭여부와 비밀번호 여부를 확인할 경우 특정 DOM에 className을 추가하여 해당 DOM을 직접적으로 핸들링한다.

/* styles/main.css 파일 */
@import 'utils';

body {
    background: $oc-gray-1;
    margin: 0px;
}

/* 성공과 실패의 css를 추가한다. */
.success {
    background: lightblue;
}

.fail {
    background: lightcoral;
}

import React , { Component } from 'react';

class NewComponent extends Component {

    state = {
         pwd: ''          // 비밀번호
        ,clicked: false   // 클릭 여부
        ,validated: false // 유효성검사 여부
    }
    
    // 임의 메서드 생성
    changedVal = (e) => {
        this.setState({
            pwd: e.target.value // 비밀번호 업데이트
        });
    }

    btnClick = () => {
        this.setState({
             clicked: true // 클릭일 경우 클릭여부를 true로 변경
            ,validated: this.state.pwd === '0000' // 비밀번호가 0000일 경우 유효성검사 여부를 true로 변경
        });
    }

    render() {
        return (
            <div>
                <p>ref 연습</p>
                <input
                    type='password'
                    name='pwd'
                    value={this.state.pwd}
                    onChange={this.changedVal} // 임의 메서드 활용
                    className={this.state.clicked ? (this.state.validated ? 'success' : 'fail') : '' }
                />
                <button onClick={this.btnClick}>검증하기</button>
            </div>
        );
    }
}

export default NewComponent;

input에서는 onChange 이벤트가 발생하면 changedVal를 호출하여 statepwd 값을 업데이트한다.
button에서 onClick 이벤트가 발생하면 btnClick을 호출하여 clicked 값과 validated 값을 검증하도록 설정하였다.

inputclassName 값은 버튼을 누르기 전에는 비어 있는 문자열을 전달하여 css가 변경되지 않고, 버튼을 누른 후에는 검증 결과에 따라 success/fail로 설정한다. 이 값에 따라 input의 색상이 변경된다.

위의 예제에서 state를 사용하여 기능을 구현했지만, state만으로 해결할 수 없는 기능이 있다. 이런 경우 ref를 사용해야한다.

01.7.1. ref 사용방법

ref를 달아야 하는 DOMref 속성을 추가할 때는 props를 설정하면 된다.
ref 값으로는 콜백 함수를 전달한다.
콜백 함수ref파라미터로 가지며, 콜백 함수 내부에서 컴포넌트멤버 변수ref를 담는 코드를 작성한다.
<input ref={(ref) => this.input=ref}/>
위에서 작성한 소스를 수정해 어떻게 작동하는지 확인한다.
<input
    ref={(ref) => this.input=ref} /* ref 추가 */
    type='password'
    name='pwd'
    value={this.state.pwd}
    onChange={this.changedVal} // 임의 메서드 활용
    className={this.state.clicked ? (this.state.validated ? 'success' : 'fail') : '' }
/>

btnClick = () => {
    this.setState({
         clicked: true // 클릭일 경우 클릭여부를 true로 변경
        ,validated: this.state.pwd === '0000' // 비밀번호가 0000일 경우 유효성검사 여부 true로 변경
    });
    this.input.focus(); // input에 포커스
}
상단의 코드를 작성 후 확인을 해보면 ref로 전달한 input을 핸들링 할 수 있다.

01.7.2. 컴포넌트에 ref 적용

DOMref 속성을 추가하듯이 컴포넌트에도 ref 속성을 추가할 수 있다.

<NewComponent ref={(ref) => {this.newComponent=ref}} />
컴포넌트ref를 적용하면 컴포넌트 내부메서드멤버 변수에도 접근할 수 있다. 또한 내부의 ref에도 접근 할 수 있다.
import React , { Component } from 'react';

class NewComponent 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} />
                <button onClick={this.btnClick}>검증하기</button>
            </div>
        );
    }
}

export default NewComponent;

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

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

export default App;

01.7.3 컴포넌트에 메서드 생성

컴포넌트에 스크롤바를 맨 아래쪽으로 내리는 메서드를 만들어본다.
import React , { Component } from 'react';

class NewComponent extends Component {
    scrollToBottom = () => { // ES6의 비구조화 할당(destructuring assignment) 문법을 사용
        const { scrollHeight, clientHeight } = this.box;
        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 ( ... );
    }
}

export default NewComponent;


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

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

export default App;
문법상으로 onClick = { this.newComponent. scrollToBottom } 같은 형식으로 작성해도 틀린 것은 아니다. 하지만 컴포넌트가 처음 렌더링될 때는 this.newComponent 값이 undefined이기 때문에 this.newComponent. scrollToBottom 값을 읽어 오는 과정에서 오류가 발생한다. 화살표 함수 문법을 사용하여 아예 새로운 함수를 만들고 그 내부에서 this.newComponent. scrollToBottom 메서드를 실행하면 (렌더링이 되어 this.newComponent 를 설정한 후) 버튼을 누를 때 this.newComponent. scrollToBottom 값을 읽어와서 실행하므로 오류가 발생하지 않는다.

※ 서로 다른 컴포넌트끼리 데이터를 교류할 때 ref를 사용한다면 물론 할 수 있지만 이것은 잘못된 것이다. 컴포넌트에 ref 를 달고 그 ref를 다른 점포넌트로 전달하고 ... 다른 컴포넌트에서 ref로 전달받은 컴포넌트의 메서드를 실행한다면 이는 리액트 사상에 어긋난 설계이다. 컴포넌트끼리 데이터를 교류할 때는 언제나 데이터를 부모 <-> 자식 흐름으로 교류해야한다.

댓글

이 블로그의 인기 게시물

01.11 리액트 컴포넌트 CSS 적용 ( CSS Module, Sass, styled-components )

01.9 React 컴포넌트 라이프사이클