Back Ground

React - ref : DOM에 이름 달기 본문

Javascript/React.js

React - ref : DOM에 이름 달기

Back 2018. 10. 20. 12:42



리액트 컴포넌트 안에서는 ID를 사용하면 안된다? 


리액트 컴포넌트 안에서도 id를 사용할 수는 있다.

JSX안에서 DOM에 id를 달면 해당 DOM을 랜더링할 때 그대로 전달된다.


<div id="my-element"></div>


하지만 특수한 경우가 아니면 권장하지 않는다.

예를 들어 같은 컴포넌트를 여러번 사용 한다고 가정해보면


HTML에서 DOM의 id는 유일해야 하는데, 이런 상황에서는 중복 id를 가진 DOM이 여러 개 생기는 잘못된 사용입니다.


ref는 전역적으로 작동하지 않고 컴포넌트 내부에만 작동하기 때문에 이런 문제가 생기지않는다.

대부분은 id를 사용하지 않고도 원하는 기능을 구현할 수 있지만,

다른 라이버리나 프레임워크와 함께 id를 사용해야 하는 상황이 발생할 수 있습니다.





ref

1
ReactDOM.render(<App /> ,documnet.getElementById('root'));
cs

이렇게 HTML에서 id를 사용하여 DOM에 이름을 다는 것처럼 

리액트 프로젝트 내부에서 DOM에 이름을 다는 방법이 있다.


바로 ref(reference의 줄임) 개념이다.


일단 특정 DOM에 작업을 해야 할 때 ref를 사용해야 할까?

그건 DOM을 꼭 직접적으로 건드려야 할 때 이다.


예를 들어 일반 순수자스크립트 및 jQuery로 만든 웹 사이트에서 

input을 검증할 때는 다음과 같이 특정 id를 가진 input에 클래스를 설정해준다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html>
<head>
<title>HTML, CSS and JavaScript demo</title>
<style>
    .sucess{
      background-color: green;
    }    
    
    .failure{
      background-color: red;
    
</style>
</head>
<script>
  function valudate(){
    var input = document.getElementById('password');
    input.className ='';
    if(input.value == '0000'){
      input.className = 'success';
    }else{
      input.className = 'failure';
    }
  }  
</script>
<body>
  <input type="password" id="password"/>
  <button onclick="validate()">Validate</button>
</body>
</html>
cs



그럼 input의 0000을 입력후 버튼을 누른다.


하지만 리액트에선 굳이 DOM에 접근하지않아도 state로 구현이 가능하다.



state를 사용하여 구현하자면

ValidationSample 컴포넌트 만들기 

 ↓

 input에 ref 달기

 ↓ 

 버튼을 누를 때마다 input에 포커스 주기





예제 컴포넌트 생성


hello-react/src 디렌터리 안에 ValidationSample.css 와 ValidationSample.js 파일을 만들어준다.


ValidationSample.css

1
2
3
4
5
6
7
8
.sucess{
  background-color: green;
}    
    
.failure{
  background-color: red;
}
    
cs



ValidationSample.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
    });
  }
  
  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.validate ? 'success' : 'failure') : ''  }
          <button onClick={this.handleButtonClick}>
            검증하기
          <button >
        />
      </div>
    )
  }
}
                           
export default ValidationSample;
 
cs

input에서는 onChange이벤트가 발생하면

handleChange를 호출하여 state의 password값을 업데이트 하게한다.



input의 className값은 버튼 누르기전비어있는 문자열을 전달

버튼을 누른 후에 는 검증 결과에 따라 success값 또는 failure값을 설정한다.


그리고 이 값에 따라서 input 색상(초록 또는 빨강)을 바꾸는 코드이다.



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


App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';
import ValidationSample from './ValidationSample'
 
class App extends Component {
  render() {
    return (
      <div className="App">
        <ValidationSample />
      </div>
    );
  }
}
 
export default App;
 
cs


이렇게 랜더링을 하게되면 password '0000' 에 따라 class명에 따라 css가 변경되는 것을 알 수있다.







ref 사용

 

ref (Reference 참조)를 달아야하는 DOM에 ref속성을 추가할 때는 props를 설정하듯 하면된다.

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

콜백 함수는 ref를 매개변수로 가지며, 콜백 함수 내부에서 컴포넌트의 멤버 변수에 ref를 담는 코드를 작성한다.

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


DOM을 꼭 사용 해야 하는 상황

앞서에는 state를 사용하여 기능구현했지만
state으로 해결 할수 없는 기능이 있다 


- 특정 input에 포커스 주기

- 스크롤 박스 조작하기

- canvas 요소에 그림 그리기 등등 


이때는 어쩔 수 없이 DOM에 직접적으로 접근해야 하는데

그때 ref 를 사용한다.


ex)

1
2
3
4
5
6
<div ref={(ref) => {ref} }>
    <a href="#">
          <span></span>
      </a>
    <p></p>
</div>
cs

이런 경우 ref는 div요소의 자식 노드까지 다 갖고 있다.








input에 ref 달기 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
    });
  }
  
  handleButtonClick = () =>{
    this.setState({
      clicked:true,
      validated: this.state.password === '0000'
    });
  }
  
  render(){
    return(
      <div>
        <input
          ref={ (ref) => this.input=ref }
          type='password'
          value={this.state.password}    
          onChange={this.handleChange}
          className={this.state.clicked ? 
            (this.state.validate ? 'success' : 'failure') : ''  }
          <button onClick={this.handleButtonClick}>
            검증하기
          <button >
        />
      </div>
    )
  }
}
                           
export default ValidationSample;
cs




버튼 onClick 이벤트 코드 수정 하여 input에 포커스를 주도록 해 본다.

this.input는 컴포넌트 내부의 input(변수) 요소를 가르키고 있으니,  

일반 DOM을 다루듯 코드를 작성하면 된다.



1
2
3
4
5
6
7
    handleButtonClick = () => {
        this.setState({
            clicked:  true,
            validated: this.state.password === '0000'
        });
        this.input.focus();
    }   
cs


this.input에는 해당 input이 (ref)참조 되어 있기에  .focus()로 포커스를 주었다.







컴포넌트에 ref 달기


리액트에서는 컴포넌트에도 ref를 달 수 있다.

이 방법은 주로 컴포넌트 내부에 있는 DOM을 

컴포넌트 외부에서 사용할 때 쓴다.


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


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


즉, 내부의 ref에도 접근 할 수 있다.

[예)  myComponent.handleClick 이나 myComponent.input 등 ] 




컴포넌트 초기 설정

먼저 ScrollBox라는 컴포넌트 파일을 만들어

JSX의 인라인 스타일링 문법으로 스크롤 박스를 만들어 그라데이션 박스를 만들어본다.


ScrollBox.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, { Component } from 'react'
 
export default class ScrollBox extends Component {
  
    render() {
        const style = {
            border : '1px solid black',   
            height : '300px',
            width : '300px',
            overflow : 'auto',
        }
 
        const innerStyle ={
            height : '650px',
            background : 'linear-gradient(white, black)',
        }
 
        return (
        <div
            style={style}
            ref={(ref) => {this.box = ref} }>
        
            <div style={
                innerStyle
            }/>
            
        </div>
        )
        console.log(this.box);
    }
}
 
cs



App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';
import ScrollBox from './ScrollBox';
 
class App extends Component {
  render() {
    return (
      <div className="App">
        <ScrollBox/>
      </div>
    );
  }
}
 
export default App;
 
cs


하면 스크롤 박스가 생기는 것을 확인한다.







확인이 됐다면 컴포넌트에 메서드 생성 한다


컴포넌트에 스크롤바를 맨 아래로 내리는 메서드를 만든다.

자바스크립트로 스크롤바를 내릴때 DOM 노드가 가진 값을 사용한다.


[ 자바스크립트 스크롤 명령어 ]

- scrollTop  : 세로 스크롤바 위치 

- scrollHeight : 해당 요소의 스크롤 박스의 높이 

- clientHeight : 해당 요소 내부 요소의 스크롤 박스 높이 


ScrollBox.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React, { Component } from 'react'
 
export default class ScrollBox extends Component {
  
    scrollToBottom = () => {
        const { scrollHeight , clientHeight } = this.box;
        console.log('scrollHeight : '+scrollHeight + ' clientHeight : ' + clientHeight);
       
        this.box.scrollTop = scrollHeight - clientHeight;
    }
  
    render() {
 
        const style = {
            border : '1px solid black',   
            height : '300px',
            width : '300px',
            overflow : 'auto',
        }
 
        const innerStyle ={
            height : '650px',
            background : 'linear-gradient(white, black)',
        }
 
        return (
        <div
            style={style}
            ref={(ref) => {this.box = ref} }>
        
            <div style={
                innerStyle
            }/>
            
        </div>
        )
        
    }
 
}
 
cs


여기서 const { scrollHeight , clientHeight } = this.box; 이부분은


이는 ES6의 비구조화 할당 문법을 사용한 것으로

const scrollHeight = this.box.scrollHeight;

const clientHeight = this.box.clientHeight;


이것과 같다.



그렇게 box 안에 스크롤의 높이들이 들어있게 된다.



App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from 'react';
import ScrollBox from './ScrollBox';
 
class App extends Component {
  render() {
    return (
      <div className="App">
        <ScrollBox ref={(ref) => {this.ScrollBox=ref}}/>
        <button onClick={() => this.ScrollBox.scrollToBottom()}>맨 밑으로 </button>
      </div>
    );
  }
}
 
export default App;
 
cs


이렇게 ScorllBox의 컴포넌트를 참조하여 

onClick 이벤트를 통해 ScorllBoxscrollToBottom()을 실행하여 스크롤의 높이를 변경하였다.


이때 주의해야 하는점이 있다.

onClick={() => this.ScrollBox.srcollToBottom()}

이부분에서 


onClick={this.ScrollBox.srcollToBottom()}

이런 형식으로 작성해도 문법상 틀린것은 아니지만


App컴포넌트가 처음 랜더링될 때는 this.scrollBox값이 아직 undefined이다. 

그래서 this.scrollBox.scrollToBottom값을 읽어오는 과정에서 오류가 발생한다.





[ 해결 방식 ]

에로우 펀션 문법을 사용하여 아예 새로운 함수를 만들고

내부에서 this.scrollBox.scrollToBottom 메서드를 실행하면,


버튼을 누를때 (이미 한 번 랜더링을 해서 this.scrollBox를 설정한 시점)

this.scrollBox.scrollToBottom값을 읽어와서 실행하므로 오류가 발생하지않는다.





'Javascript > React.js' 카테고리의 다른 글

React - 컴포넌트 반복  (0) 2018.10.28
React - ( <mark up> ) 표현  (0) 2018.10.27
React - 이벤트 핸들링  (0) 2018.10.20
React - 컴포넌트 [props , state]  (0) 2018.10.19
React - JSX  (0) 2018.10.19
Comments