HTML에선 특정한 요소에만 css를 적용시키기 위해서 id를 사용했다. 리액트에서도 id와 비슷한 역할을 하는 ref(=reference)를 통해 DOM에 이름을 달 수 있다.
JSX안에서도 id를 지정할 수는 있지만, 컴포넌트가 반복 사용되는 경우 id의 유일성이 훼손된다. 반면 ref는 컴포넌트 내부에서만 작동하기 때문에 이러한 문제에서 자유롭다. 직접 예제를 따라 작성해보며 ref를 이해해보자.
(당장은 Hooks에 대한 지식이 없어 클래스형 컴포넌트로만 실습을 진행한다.)
리액트에서 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 사용 방법에 대해 알아보자.
<input ref={(ref) => {this.input = ref}} />
이렇게 작성해주면 앞으로 this.input
은 input요소의 DOM을 가리킨다. 이 때 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요소를 보다 편하게 컨트롤 할 수 있다.
컴포넌트에도 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 컴포넌트에 메소드를 추가해줬다.
이제 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를 사용해야만 구현할 수 있는 기능인지 생각해보도록 하자. 냅다 아는 기능 써버리지 말고 내가 정확히 뭘 구현하는지 인지하는 습관을 들이도록 하자..