Introduction
일반적으로 Javascript에서 DOM에 직접 접근할 때는 document.getElementById
등을 통해서 접근하고는 합니다. 하지만 React에서는 ref
라는 것을 사용해서 접근하는데요, ref란 무엇이고, 기존의 방법과는 무엇이 다른지, 어떻게 사용하면 되는지 알아보겠습니다.
What is ref?
우선 ref
란, reference
의 줄임말입니다. 노드나 컴포넌트에 ref 값을 만들어서 해당 요소의 값을 얻어오거나 해당 요소를 제어하는 데 사용되죠. React팀에서는 아래와 같은 경우에 ref를 사용할 것을 권장하고 있습니다.
내용을 보시면 알 수 있겠지만 React에서 만들고 통제하는 요소(제어 컴포넌트; Controlled Component
)가 아닌 경우를 통제하고자 하는 경우에 사용을 권장한다고 하는 것 같습니다. React에서 통제하지 못한다는 건, 바꿔서 말하면 React의 라이프사이클을 따르지 않는다는 의미이기도 합니다. 왠지 side-effect가 발생할 것 같은 냄새가 나지 않나요?
ref를 사용해보면, current
라는 프로퍼티를 하나 가지고 있는 객체가 반환되는 걸 볼 수 있습니다. 해당 프로퍼티 안에 DOM node가 존재하고요. 코드는 아래와 같습니다.
// functional component
const refEx = () => {
const ref= useRef()
useEffect(() => {
console.log(ref) // { current: div }
}, [ref])
return (
<div ref={ref} />
)
}
// class component
class refEx extends Component {
ref = createRef()
componentDidMount() {
console.log(this.ref) // { current: div }
}
render() {
return (
<div ref={this.ref} />
)
}
}
위는 함수형 컴포넌트, 아래는 클래스형 컴포넌트에서 ref를 사용하는, 완벽하게 동일한 동작을 보여주는 예시입니다. 자세히 보시면 ref를 만들기 위해 사용하는 함수가 서로 다른 것을 알 수 있는데요, useRef
는 함수형에서 사용되는 hook
이라는 건 알겠는데, 함수형에서 createRef
를 사용하면 안되는 걸까요?
정답은 가능은 하다
입니다. 하지만 함수형 컴포넌트와 클래스형 컴포넌트의 특성이 다르기 때문에 다르게 사용되고 있는 것이지요. 각 컴포넌트의 특성을 상세하게 설명하는 것은 해당 글의 논지와 벗어나기 때문에 간단히 설명하자면, 함수형 컴포넌트는 말 그대로 함수이기에 상태가 바뀔 때마다 매번 새롭게
호출됩니다. 반면에 클래스형 컴포넌트는 render함수
안에 있는 부분만이 새롭게 호출됩니다. 따라서 함수형에서 createRef를 사용한다면 상태가 유지되지 않고 계속해서 새로운 ref를 생성할 겁니다.
DOM API
상술한 바와 같이 Javascript에서는 document
객체에 포함된 querySelector
getElementById
등의 DOM API를 통해서 ref와 동일한 작업을 수행합니다. 그리고 대부분의 경우, 이렇게 해도 큰 문제가 생기지는 않습니다. third party library에서 ref를 강제하는 경우가 아니라면요. 그럼 왜 굳이 익숙한 DOM API를 자제하고 ref를 사용하도록 권장하는 걸까요?
우선 위에서도 설명했듯 ref를 사용하는 편이 React 내부에서 더 믿음직하기 때문입니다. DOM API로 특정 요소를 가져오려고 시도하는 경우, 아직 render되지 않은 요소는 선택되지 않아서 undefined를 반환할 가능성이 높습니다. 그에 비해 ref는 React의 라이프사이클을 충실히 따르고 있죠.
또, 만약 여러 개의 요소들 중에 하나를 가져와야 하는 경우를 생각해볼 수 있겠습니다. 분명 querySelector, getElementById 등의 DOM API로는, 로직에 따라 꽤 번거로운 코드를 작성해야 할 수도 있습니다.