ref로 DOM에 이름 달기

nasagong·2023년 2월 9일
0

React

목록 보기
6/15
post-thumbnail

📚 들어가며

HTML에선 특정한 요소에만 css를 적용시키기 위해서 id를 사용했다. 리액트에서도 id와 비슷한 역할을 하는 ref(=reference)를 통해 DOM에 이름을 달 수 있다.

JSX안에서도 id를 지정할 수는 있지만, 컴포넌트가 반복 사용되는 경우 id의 유일성이 훼손된다. 반면 ref는 컴포넌트 내부에서만 작동하기 때문에 이러한 문제에서 자유롭다. 직접 예제를 따라 작성해보며 ref를 이해해보자.

(당장은 Hooks에 대한 지식이 없어 클래스형 컴포넌트로만 실습을 진행한다.)

1. ref가 사용되는 상황

리액트에서 ref는 DOM을 직접적으로 컨트롤 해야하는 상황에 사용된다. 잘 이해가 되지 않을테니 간단한 프로젝트의 코드를 보며 납득해보자.

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'
        })
    }
    handleKeyPress = e =>{
        if(e.key === 'Enter'){
            this.handleButtonClick();
        }
    }

    render(){
        return(
            <div>
                <input
                type = "password"
                value = {this.state.password}
                onChange = {this.handleChange}
                className = {this.state.clicked ? (this.state.validated?'success' : 'failure'):''}
                onKeyPress = {this.handleKeyPress}
                />
                <button onClick={this.handleButtonClick}>검증하기</button>
            </div>
        )
    }
}
export default ValidationSample;

앞 챕터에서 배운 내용들을 통해 구현된 비밀번호 검증 페이지다. 이제 이 프로젝트에 ref를 사용해보기에 앞서 ref 사용 방법에 대해 알아보자.

콜백 함수를 통한 ref 설정

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

이렇게 작성해주면 앞으로 this.input은 input요소의 DOM을 가리킨다. 이 때 ref의 이름은 자유롭게 지어도 된다.

createRef를 통한 ref 설정

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

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

다른 방법으로는 리액트에 내장된 createRef함수를 사용하는 것이 있다. 이제 ref를 사용해 비밀번호 검증 프로젝트에 기능을 추가해보자.

handleButtonClick = () =>{
        this.setState({
            clicked : true,
            validated : this.state.password === '0000'
        });
        this.inpuuut.focus();
    }
render(){
        return(
            <div>
                <input
                type = "password"
                value = {this.state.password}
                onChange = {this.handleChange}
                className = {this.state.clicked ? (this.state.validated?'success' : 'failure'):''}
                onKeyPress = {this.handleKeyPress}
                ref ={(ref)=>this.inpuuut = ref}
                />
                <button onClick={this.handleButtonClick}>검증하기</button>
            </div>
        )
    }

수정된 부분만 확인해보자. 태그 내에서 콜백함수를 통해 ref를 inpuuut으로 지정했고, handleButtonClick메소드에서 this.inpuuut.focus()를 호출해줬다. 렌더링해본 결과 버튼을 누르면 input으로 focus가 이동한다.

이처럼 ref를 사용하면 특정 DOM요소를 보다 편하게 컨트롤 할 수 있다.

2. 컴포넌트에 ref 달기

컴포넌트에도 ref를 달 수 있다. 이는 보통 컴포넌트 내부에 있는 DOM을 외부에서 접근해야 할 때 사용한다.

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

가령 이런 경우에는 MyComponent 내부의 메서드 및 멤버 변수에 접근할 수 있게 된다.
example : MyComponent.MethodName

스크롤 박스가 있는 컴포넌트를 하나 만들어보자. 이 컴포넌트를 통해 컴포넌트에 ref를 지정하고 활용하는 방법을 알아보자.

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

최상위 DOM에 this.box를 ref로 설정해주고, 스타일 객체를 통해 내부를 그라데이션으로 채워줬다. 이제 버튼 하나만 누르면 박스의 bottom으로 스크롤을 이동시키는 기능을 추가할 것이다.

// ScrollBox.js
class ScrollBox extends Component {
  scrollToBottom = () => {
    const { scrollHeight, clientHeight } = this.box;
    this.box.scrollTop = scrollHeight - clientHeight;
  };
  render() {...};
    return (...);
  }
}
export default ScrollBox;

ScrollBox 컴포넌트에 메소드를 추가해줬다.

  • scrollTop : 세로 스크롤바 위치 (0~350)
  • scrollHeight : 스크롤이 있는 박스 안의 div 높이 (650)
  • clientHeight : 스크롤이 있는 박스의 높이 (300)
메소드에 사용된 스크롤 관련 DOM노드들은 위와 같다. scrollTop 값을 scrollHeight - clientHeight 로 설정해주면 맨 아래쪽으로 이동항 수 있을 것이다. 헷갈린다면 아래 이미지를 참고해보자.


이미지 출처

이제 scrollToBottom 메소드를 App컴포넌트에서 사용해 볼 것이다. 예고한대로 ref를 사용해 접근한다.

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

렌더링하는 컴포넌트 자체에 ref를 통해 접근한 후 메소드르 꺼내 쓰고 있다. 버튼에는 정상적으로 함수가 전달되어 App을 렌더링해보면 아래처럼 스크롤을 맨 아래로 이동시키는 버튼이 생긴다.

왜 함수를 바로 전달하지 않는가?

<button onClick={()=>this.scrollBox.scrollToBotom()}>

코드를 보면 함수를 위와 같은 형태로 전달하고 있다. 문법상으로는

<button onClick={this.scrollBox.scrollToBotom}>

위처럼 작성하는 것도 문제가 되진 않지만, 버튼이 App에서 렌더링되고 있다는 점을 생각해야 한다. App이 처음 렌더링되는 시점에선 this.scrollBox값이 undefined이기에 바로 이벤트로 전달될 경우 오류가 발생할 수 있다. 따라서 화살표 함수로 감싸줘 절차를 하나 추가하여 버튼을 누른 시점 (scrollBox가 렌더링 된 시점)에 메소드를 읽어와야 오류가 발생하지 않는다.

🤔 마치며

ref는 컴포넌트 내에서 DOM에 직접 접근해야 할 때 사용한다. 앞서 App에서 하위 컴포넌트의 요소를 끌어 쓰는 예시를 봤는데, 이걸 보고 ref를 컴포넌트끼리 데이터를 교환하는 수단으로 생각해선 안 된다. 컴포넌트에 ref를 달고 전달에 전달을 거듭하다보면 유지보수하기 어려운 복잡한 코드가 탄생하게 된다.

또한, ref를 사용하기 전에는 이게 꼭 ref를 사용해야만 구현할 수 있는 기능인지 생각해보도록 하자. 냅다 아는 기능 써버리지 말고 내가 정확히 뭘 구현하는지 인지하는 습관을 들이도록 하자..

profile
잘쫌해

0개의 댓글