DOM에 이름 달기-ref

Posted by yunki kim on June 28, 2021

  통상적으로 HTML에서 DOM에 이름을 달때는 id를 사용한다. 또 한 리엑트 프로젝트에서도 이를 사용한다. public/index.html에서는 body의 div id가 root로 되어 있고 src/index.js에서는 document.getElementById('root')를 사용한다. 

  리엑트 프로젝트 내부에서는 ref(reference)를 통해 DOM에 이름을 달 수 있다. 물론 리엑트 컴포넌트 내부에서도 id를 사용할 수 있다. 하지만 id는 unique해야 되고 전역적으로 작동하기 때문에 중복된 id를 가진 DOM이 생길 수 있다. 하지만 ref는 해당 컴포넌트 내부에서만 작동한다. 만약 굳이 id를 사용해야 한다면 id뒤에 추가 텍스트를 넣어서 중복 id가 생기는 것을 방지하자(id=button01, button02, ....)

 

ref의 사용 목적

  리엑트에서는 DOM을 반드시 직접 건드려야 할 때 ref를 사용한다. 바닐라 또는 jquery의 경우 DOM에 접근해야 어떠한 동작을 할 수 있다. 예를 들어 로그인 비밀번호 검증. 하지만 리엑트는 이러한 작업을 state로 할 처리할 수 있다.

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
import React, {useState}  from 'react';
 
const CheckPassword = () => {
    const [passwd, setPasswd] = useState('');
    const onChange = (e) => {
        setPasswd(e.target.value);
    }
    const onClick = () => {
        alert(passwd === '000' ? 'success' : 'fail');
        setPasswd(passwd, '');
    }
    return(
        <div>
            <input type="password"
                   placeholder={"password"}
                   name={"passwd"}
                   onChange={onChange}
            />
            <button
                onClick={onClick}
            >confirm</button>
        </div>
    );
};
 
export default CheckPassword;
cs

  위 예제의 경우 state만을 사용 했지만 간혹 state로 처리할 수 없는 상황이 발생한다. 이런 상황으로는 다음과 같은 경우가 있다.

    1. 특정 input에 포커스 주기

    2. 스크롤 박스 조작

    3. Canvas요소에 그림 그리기

ref 사용 방식

  1. 콜백 사용

    ref를 사용하고자 하는 요소에 ref 콜백 함수를 props로 전달해 주면 된다. 그 후 함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정해 준다.

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

    위 코드와 같이 작성을 하게 되면 this.input은 해당 input의 DOM을 가리키게 된다. 

  2. createRef를 통한 ref설정

    이는 리엑트 V16.3부터 도입된 방식이다.

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
import React, {Component} from 'react';
 
class CreateRef extends Component {
    input = React.createRef();
 
    handleFocus = () => {
        this.input.current.focus();
    }
 
    onChange = (e) => {
        console.log(this.input.current);//ref설정 이후 DOM에 접근 할려면 .current사용
    }
 
    render(){
        return(
            <div>
                <input type="text"
                       ref={this.input}
                       onChange={this.onChange}
                />
            </div>
        );
    }
}
 
export default CreateRef;
cs

컴포넌트에 ref달기

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

  위 예제와 같이 사용하면 된다. DOM에 ref를 다는 방식과 다를게 없다. 이 방식은 주로 컴포넌트 내부의 DOM을 컴포넌트 외부에서 사용할때 사용된다.  이렇게 하면 내부의 메서드, 멤버변수에 사용할 수 있다.

  예제

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//App.js
import logo from './logo.svg';
import React, {Component} from 'react';
import './App.css';
import ValidationSample from "./validation-sample";
import CreateRef from "./create-ref";
import ScrollBox from "./ScrollBox";
 
class App extends Component {
    render() {
        return (
            <div>
                <ScrollBox ref={(ref) => this.scrollBox=ref}/>
                <button onClick={() => this.scrollBox.scrollToBottom()}>
                    to bottom
                </button>
            </div>
        );
    }
}
 
export default App;
 
//ScrollBar.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>
            </div>
        )
    }
}
 
export default ScrollBox;
cs

  위 코드에서 App.js의 button태그의 onClick부분을 onClick={this.scrollBox.scrollBottom}같은 형식으로 작성을 해도 된다. 하지만 컴포넌트가 처음 렌터링 되었을때 this.scrollBox값이 undefined이기 떄문에 this.scrollBox.scrollToBottom값을 읽어 오는 과정에서 오류가 발생한다. 따라서 함수 내부에서 this.scrollBox.scrollToBottom 메서드를 실행하면 버튼을 누를 때 이 메서드의 값을 읽어와서 실행하기 때문에 오류가 발생하지 않는다.

 

 함수형 컴포넌트에서는 useRef라는 Hook함수를 사용한다.

 

오해

  여기서 오해할 수 있는 부분이 서로 다른 컴포넌트 끼리 데이터를 교류할 때 ref를 사용한다면 이는 잘못된 것이다. 할 수는 있지만 컴포넌트에 ref를 달고 그 ref를 다른 컴포넌트에 전달하기를 반복해야 한다. 이는 리엑트의 사상에 어긋나는 설계이다. 컴포넌트끼리 데이터 교류는 언제나 데이터를 부모 <-> 자식 흐름으로 교류해야 한다.