관리 메뉴

거니의 velog

(14) ref:DOM 에 이름 달기 2 본문

React_리액트 시작

(14) ref:DOM 에 이름 달기 2

Unlimited00 2023. 11. 30. 17:30

2. ref 사용

* 이제 프로젝트에서 ref를 사용해 보자. ref를 사용하는 방법은 두 가지이다.


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

* ref를 만드는 가장 기본적인 방법은 콜백 함수를 사용하는 것이다. ref를 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달해 주면 된다. 이 콜백 함수는 ref 값을 파라미터로 전달받는다.  그리고 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정해 준다.

* 콜백 함수 사용 예시

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

* 이렇게 하면 앞으로 this.input은 input 요소의 DOM을 가리킨다. ref의 이름은 원하는 것으로 자유롭게 지정할 수 있다. DOM 타입과 관계없이 this.superman = ref 처럼 마음대로 지정한다.


(2) createRef를 통한 ref 설정

* ref를 만드는 또 다른 방법은 리액트에 내장되어 있는 createRef라는 함수를 사용하는 것이다. 이 함수를 사용해서 만들면 더 적은 코드로 쉽게 사용할 수 있다. 이 기능은 리액트 v16.3부터 도입되었으며, 이전 버전에는 작동하지 않는다.

import React, { Component } from 'react';

class createRef extends Component {
  input = React.createRef();

  handleFocus = () => {
    this.input.current.focus();
  };

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

export default createRef;

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

* 설정한 뒤 나중에는 ref를 설정해 준 DOM에 접근하려면 this.input.current를 조회하면 된다. 콜백 함수를 사용할 때와 다른 점은 이렇게 뒷부분에 .current를 넣어 주어야 한다는 것이다.

* 지금까지 콜백 함수 혹은 createRef를 사용하여 ref를 만드는 방법을 배워 보았다. 앞으로 두 가지 방법 중에서 편한 방법을 사용하자.

* 이번 장에서는 주로 콜백 함수를 사용하는 방식으로 ref를 다루어 보자.


(3) 적용

* 이전에 만든 ValidationSample 컴포넌트의 렌더링 결과를 다시 한 번 살펴 보자.

input에 포커스

* input 요소를 클릭하면 포커스가 되면 텍스트 커서가 깜박인다.

버튼에 포커스

* 버튼을 누르면 포커스가 버튼으로 넘어가면서 왼쪽 input 요소의 텍스트 커서가 더 이상 보이지 않는다.

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

[1] input에 ref 달기

* 이전에 배운 대로 콜백 함수를 사용하여 ValidationSample 컴포넌트에도 ref를 달아 보자.

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

[2] 버튼 onClick 이벤트 코드 수정

* 버튼에서 onClick 이벤트가 발생할 때 input에 포커스를 주도록 코드를 수정해 보자. 이제 this.input이 컴포넌트 내부의 input 요소를 가리키고 있으나, 일반 DOM을 다루듯이 코드를 작성하면 된다.

  handleButtonClick = () => {
    this.setState({
      clicked: true,
      validated: this.state.password === '0000',
    });
    this.input.focus();
  };

* 코드를 저장하고, 웹 브라우저에서 페이지를 열어 버튼을 눌러 보자. 포커스가 input으로 바로 넘어간다.

input으로 넘어간 포커스


3. 컴포넌트 ref 달기

* 리액트에서는 컴포넌트에서 ref를 달 수 있다. 이 방법은 주로 컴포넌트 내부에 있는 DOM을 컴포넌트 외부에서 사용할 때 쓴다. 컴포넌트에서 ref를 다는 방법은 DOM에 ref를 다는 방법과 똑같다.


(1) 사용법

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

* 이렇게 하면 MyComponent 내부의 메서드 및 멤버 변수에도 접근할 수 있다. 즉, 내부의 ref에도 접근할 수 있다(예: myComponent, handleClick, myComponent.input 등).

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

* 이번 실습은 다음 흐름으로 진행한다.


(2) 컴포넌트 초기 설정

* 먼저 ScrollBox 라는 컴포넌트 파일을 만들자. JSX 인라인 스타일링 문법으로 스크롤 박스를 만들어 보자. 그 다음에는 최상위 DOM에 ref를 달아주자.

[1] 컴포넌트 파일 생성

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>
      </div>
    );
  }
}

export default ScrollBox;

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

* 기존 ValidationSample을 지우고, 방금 만든 ScrollBox 컴포넌트를 렌더링 하자.

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)

* 스크롤바를 맨 아래쪽으로 내리려면 scrollHeight에서 clientHeight 높이를 빼면 된다.

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

export default ScrollBox;

* scrollToBottom 메서드의 첫 번째 줄에서는 ES6의 비구조화 할당 문법을 사용했다.

* 이렇게 만든 메서드는 부모 컴포넌트인 App 컴포넌트에서 ScrollBox에 ref를 달면 사용할 수 있다.

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

* 그럼 App 컴포넌트에서 ScrollBox에 ref를 달고 버튼을 만들어 누르면, ScrollBox 컴포넌트의 scrollToBottom 메서드를 실행하도록 코드를 작성해 보겠다.

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.scrollToBottom} 같은 형식으로 작성해도 틀린 것은 아니다. 하지만 컴포넌트가 처음 렌더링될 때는 this.scrollBox 값이 undefined이므로 this.scrollBox.scrollToBottom 값을 읽어 오는 과정에서 오류가 발생한다. 화살표 함수 문법을 사용하여 아예 새로운 함수를 만들고 그 내부에서 this.scrollBox.scrollToBottom 메서드를 실행하면, 버튼을 누를 때(이미 한 번 렌더링을 해서 this.scrollBox를 설정한 시점) this.scrollBox.scrollToBottom 값을 읽어 와서 실행하므로 오류가 발생하지 않는다.

* 자, 이제 코드를 지정하고 웹 브라우저에서 맨 밑으로 버튼을 눌러보자.

맨 밑으로 버튼을 눌러 스크롤바를 맨 아래쪽으로 이동


4. 정리

* 컴포넌트 내부에 DOM을 직접 접근해야 할 때는 ref를 사용한다. 먼저 ref를 사용하지 않고도 원하는 기능을 구현할 수 있는지 반드시 고려한 후에 활용해야 한다.

* 이 시점에서 오해할 수 있는 부분은, 서로 다른 컴포넌트끼리 데이터를 교류할 때 ref를 사용한다면 이는 잘못된 사용법이다. 물론 할수는 있으나, 컴포넌트에 ref를 달고 그 ref를 다른 컴포넌트로 전달하고 ...... 다른 컴포넌트에서 ref 로 전달 받은 컴포넌트의 메서드를 실행하고 ...... 하지만 이 방법은 리액트 사상에 어긋난 설계이다. 앱 규모가 커지면 마치 스파게티처럼 구조가 꼬여 버려서 유지 보수가 불가능하게 된다. 컴포넌트끼리 데이터를 교류할 때는 언제나 데이터를 부모 <-> 자식 흐름으로 교류해야 한다. 나중에 리덕스 혹은 Context API를 사용하여 효율적으로 교류하는 방법을 배울 것이다.

* 아직 함수형 컴포넌트에서 ref를 사용하는 것은 배우지 않았는데, 함수형 컴포넌트에서 useRef라는 Hook 함수를 사용한다. 사용법은 이 장에서 배운 React.createRef와 유사하다. 이에 관련된 내용은 추후에 알아보도록 하자.

'React_리액트 시작' 카테고리의 다른 글

(16) 컴포넌트 반복 2  (1) 2023.12.01
(15) 컴포넌트 반복 1  (0) 2023.12.01
(13) ref:DOM 에 이름 달기 1  (0) 2023.11.30
(12) 이벤트 핸들링 2  (0) 2023.11.30
(11) 이벤트 핸들링 1  (2) 2023.11.30