일반적인 HTML에서 DOM 요소에 이름을 달 때는 id
를 사용한다.
<div id="element"></div>
이렇듯, 리액트 프로젝트 내부에서 DOM에 이름을 다는 방법이 바로 ref(reference)이다.
리액트 컴포넌트에서 id를 사용하면 안될까? 사용할 수는 있지만 특수한 경우가 아니면 권장되지 않는 방법이다. HTML에서 DOM의 id는 유일해야 하는데, 리액트에서는 같은 컴포넌트를 여러 번 사용할 수 있다보니 중복된 id를 가진 DOM이 여러 개 생길 수 있다.
DOM을 반드시 직접적으로 건드려야 할 때
ref를 사용한다.
ValidationSample.js
import React, { Component } from 'react'
import "./ValidationSample.css";
export default class ValidationSample extends Component {
state = {
password: '',
clicked: false,
validated: false
}
handleChange = (e) => {
this.setState({
password: e.target.value
});
}
handleButtonClick = (e) => {
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') : null}
/>
<button onClick={this.handleButtonClick}>Validate</button>
</div>
)
}
}
ValidationSample.css
.success {
background-color: lightgreen;
}
.failure {
background-color: lightcoral;
}
ref를 만드는 가장 기본적 방법은 콜백 함수를 사용하는 것이다.
ref을 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달해 주면 된다.
콜백 함수는 ref 값을 파라미터로 전달 받는다.
그리고 함수 내부에서 파라미터로 받은 ref을 컴포넌트의 멤버 변수에 선언해 준다.
<input ref={(ref) => {this.input=ref}} />
또 하나의 방법은 리액트에 내장되어 있는 createRef 함수를 사용한다. (리액트 v16.3 버전부터 도입되었으며, 그보다 이전 버전에서는 작동하지 않는다)
export default class RefSample extends Component {
input = React.createRef();
handleFocus = () => {
this.input.current.focus();
}
render() {
return (
<div>
<input ref={this.input} />
</div>
);
}
}
createRef
를 이용하려면 컴포넌트 내부에서 멤버 변수로 React.createRef()
를 담아 주어야 한다. 그리고 ref props로 요소에 해당 변수를 넣어주면 된다.
설정한 뒤에 ref를 설정한 DOM에 접근하려면 this.input.current
를 조회하면 된다.
위의 예시 코드 ValidationSample 코드를 다시 가져와서, 실행해보면
Validate 버튼을 누르면, input 입력창에 포커스가 풀려서 입력할 수 없게 된다.
이제 콜백 함수를 이용해서 input에 ref를 달아 보자.
<input
ref={(ref) => (this.input = ref)}
type="password"
value={this.state.password}
onChange={this.handleChange}
className={
this.state.clicked
? this.state.validated
? "success"
: "failure"
: ""
}
/>
이제 this.input
이 컴포넌트 내부의 input을 가리키고 있다.
그리고 buttonClick 함수를 수정해 준다.
handleButtonClick = (e) => {
this.setState({
clicked: true,
validated: this.state.password === "0000",
});
this.input.focus();
};
focus를 this.input
에게 주어서 다시 바로 입력할 수 있게끔 해 준다.
컴포넌트에 ref 다는 방법도 DOM에 ref 다는 방법과 똑같다.
<MyComponent ref={(ref) => {this.myComponent=ref}} />
이렇게 하면 MyComponent 내부의 메서드 및 멤버 변수에 접근할 수 있다.
즉 내부의 ref에도 접근할 수 있다. (myComponent.handleClick
등등)
ScrollBox.js
export default class RefSample extends Component {
input = React.createRef();
handleFocus = () => {
this.input.current.focus();
}
render() {
return (
<div>
<input ref={this.input} />
</div>
);
}
}
스크롤 박스가 생성 되었다.
컴포넌트에 스크롤바를 맨 아래쪽으로 내리는 메서드를 만들 것이다.
자바스크립트에서 스크롤바를 내릴 때는 DOM 노드의 다음 값들을 사용한다.
스크롤바를 맨 아래로 내리려면 scrollHeight - clientHeight를 하면 될 것이다.
ScrollBox.js
scrollToBottom = () => {
const { scrollHeight, clientHeight } = this.box;
this.box.scrollTop = scrollHeight - clientHeight;
};
다음과 같은 함수를 구현해 준 뒤에 App.js에서
class App extends Component {
render() {
return (
<div>
<ScrollBox ref={(ref) => {this.scrollBox = ref}} />
<button onClick={() => {this.scrollBox.scrollToBottom()}}>맨 밑으로</button>
</div>
)
}
}
ScrollBox에 ref를 달고 버튼을 만들어 누르면 ScrollBox 컴포넌트 안의 scrollToBottom 메서드를 실행할 수 있다.
여기서 주의할 점이 App.js의 button onClick 함수인데, 그냥 onClick={this.scrollBox.scrollToBottom}
이라고 작성해도 틀린 것은 아니다.
하지만 컴포넌트가 처음 렌더링될 때는 this.scrollBox
가 undifined
이므로 this.scrollBox.scrollToBottom 값을 읽어오는 데 오류가 발생한다.
그래서 화살표 함수를 사용하여 새로운 함수를 만들고, 그 내부에서 this.scrollBox.scrollToBottom 함수를 호출하는 방식으로 실행하면, 버튼을 누를 때 값을 읽어 와서 실행하므로 오류가 발생하지 않는다.