안 쓰던 블로그

React-ref (DOM에 이름 달기) 본문

Web/React

React-ref (DOM에 이름 달기)

proqk 2020. 7. 7. 16:41
반응형

일반 HTML에서 DOM 요소에 이름을 달 때 id를 사용한다

    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>

 

기본 생성 프로젝트의 index.js에도 id가 root인 요소에 리액트 컴포넌트를 렌더링하라는 코드가 있음

ReactDOM.render(
    <App />,  document.getElementById('root')
);

 

HTML에서 id를 쓴다면 리액트에서는 ref를 쓴다

(리액트에서도 id를 쓸 수는 있지만 중복 id 등의 문제가 있어서 권장하지 않는다)

 

ref는 DOM을 꼭 직접적으로 건드려야 할 때 사용한다

 

그냥 state사용

hello-react/src디렉터리 안에 ValidationSample.css 와 ValidationSamplejs 생성

.success{
    background-color: lightgreen;
}
.failure{
    background-color: lightcoral;
}

.css

 

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

    handleButtionClick=()=>{
        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.handleButtionClick}>검증하기</button>
            </div>
        );
    }
}

export default ValidationSample;

.js

input에서 onChange면 handleChange를 호출해서 state의 password값을 업데이트하기 시작한다

button에서 onClick이 발생하면 clicked값을 true로 바꿨고 valicated값을 설정한다

className에서는 버튼을 누르기 전이면 ''을 리턴,

버튼이 눌렸다면 clicked가 참이면 success값 리턴, 아니면 failure값을 리턴한다

 

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

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

export default App;

App.js도 수정해준다

 

참이면 초록색, 거짓이면 빨간색이 표시된다

 

DOM을 꼭 사용할 수밖에 없는 상황

방금 예제에서는 state로 구현했지만 state를 쓸 수 없는 상황이 있다

 

-특정 input에 포커스 주기

-스크롤 박스 조작

-Canvase요소 그림 그리기 등

 

이럴 때는 DOM에 직접 접근해야 하기 때문에 ref를 사용한다

 

ref사용

ref를 달아야 하는 DOM에 ref속성을 추가한다. 이때는 props를 설정하듯이 하면 된다

ref값으로는 콜백 함수를 전달한다

콜백 함수는 ref를 파라미터로 가지며, 콜백 함수 내의 컴포넌트 멤버 변수에 ref를 담는다

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

 

적용

전의 예제에서는 input에다가 값을 넣고 버튼을 클릭하면 깜빡거리는 포커스가 사라졌다

그래서 다시 input을 눌러서 포커스를 해줘야 했다

 

ValidationSample.js의 일부분을 아래와 같이 수정해본다

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

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

이제 검증하기 버튼을 눌러도 input박스에서 포커스가 사라지지 않는다(=포커스가 input으로 다시 넘어간다)

 

컴포넌트에 ref달기

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

이렇게 하면 MyComponent 내부 메서드 및 멤버 변수에도 접근할 수 있따

예를 들어 myComponent.handleClick, myComponent.input 등등

 

여기서는 스크롤바를 아래로 내리는 작업을 부모 컴포넌트에서 실행해본다

src에 ScrollBox.js 생성

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;

 

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

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

export default App;

App.js 수정

 

스크롤이 생겼다

 

컴포넌트에 메서드 생성

메서드로 스크롤바를 내린다

DOM노드가 가진 값이 있다

 

-scrollTop: 세로 스크롤바 위치(0~350)

-scrollHeight: 스크롤 박스 내부의 높이(650)

-clientHeight: 스크롤 박스 외부의 높이(300)

 

스크롤 바를 맨 아래쪽으로 내리려면 scrollHeight-clientHeight를 하면 된다

 

ScrollBox.js 수정

import React, { Component } from 'react';

class ScrollBox extends Component {
    scrollToBottom=()=>{
        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 (
            <div
                style={style}
                ref={(ref)=>{this.box=ref}}>
                <div style={innerStyle}/>
            </div>
        );
    }
}

export default ScrollBox;

여기서

const {scrollHeight, clientHeight}=this.box;

부분은 아래와 같은 의미이다

        const scrollHeight=this.box.scrollHeight;
        const clientHeight=this.box.clientHeight;

 

App.js 수정

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을 할 때

onClick={this.scrollBox.scrollBottom} 같은 형식으로 했다면 문법 상으로는 틀리지 않았지만

this.scrollBox값이 처음 렌더링 되면 undefined이므로 값을 읽어오면서 오류가 난다

 

그래서 화살표 함수 문법으로 새로운 함수를 만들어서 scrollBottom메서드를 실행하는 식으로 해서

로딩하면서 렌더링은 한 번 하니까 this.scrollBox를 설정해버리고

버튼을 누를 때에 scrollBottom값을 읽어오는 식으로 해야 오류가 나지 않는다

 

 

결과-버튼을 누르면 맨 밑으로 이동한다

 

정리

-컴포넌트 내부에서 DOM에 직접 접근해야 할 때는 ref를 사용한다

-하지만 ref를 사용하지 않고 구현할 수 있을지 먼저 생각해본다

-예를 들어 서로 다른 컴포넌트끼리 데이터 교류를 할 때 ref를 사용하면 잘못된 것이다(서로 ref를 주고 ref를 또 다른 곳에 주고 주고 주고.. 하다가 코드 구조가 꺼임)

-컴포넌트끼리 데이터를 교류할 때는 언제나 데이터를 부모<->자식 흐름으로 교류해야 한다

-나중에 리덕스를 사용하여 효율적으로 교류하는 방법도 공부하면 좋다

 

 

 

참고한 책-리액트를 다루는 기술

반응형
Comments