Ref 와 DOM
Ref 는 render 메소드에서 생성된 DOM 노드 혹은 React 엘리먼트 객체에 접근할 수 있는 방법을 제공합니다.
전형적인 React 데이터 흐름에서는, 부모 컴포넌트에서 자식 엘리먼트를 조작하기 위해 props 만을 사용합니다.
즉, 자식 엘리먼트를 수정하기 위해 새 prop 을 가지고 다시 렌더링을 해줍니다.
하지만 가끔은 전형적인 데이터 흐름 밖에서 자식을 명령형으로 변경해야 할 필요가 있습니다.
여기서 변경될 자식이란 React 컴포넌트의 인스턴스일 수도 있고, DOM 엘리먼트일 수도 있습니다.
React 는 양쪽 경우 모두를 위한 비상구를 제공합니다.
언제 ref 를 사용해야 하나요?
Ref 의 바람직한 사용 사례로 다음과 같은 것을 들 수 있습니다
- 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때
- 명령형 애니메이션을 발동시킬 때
- 서드파티 DOM 라이브러리를 통합할 때
선언적으로 할 수 있는 작업에 대해서는 ref 의 사용을 피하세요.
예를 들어, Dialog 컴포넌트에 open()과 close()라는 메소드를 두는 대신 isOpen 과 같은 prop 을 넘겨주세요.
Ref 의 남용은 금물입니다
여러분의 앱에 “어떤 일이 일어나게”하기 위해 ref 를 사용하는 쪽으로 마음이 기울 수 있습니다.
이 때에는, 잠시 작업을 멈추고 앱의 상태를 컴포넌트 계층의 어떤 부분에서 소유해야 하는지를 다시 한 번 생각해보세요.
많은 경우, 상태를 “소유”해야 할 적절한 장소는 계층의 상위에 있는 컴포넌트라는 결론이 날 것입니다.
Ref 생성하기
Ref
는 React.createRef()
를 통해 생성한 뒤 React 엘리먼트의 ref 어트리뷰트에 붙여줄 수 있습니다.
Ref 는 대개 컴포넌트의 인스턴스가 만들어질 때 인스턴스의 속성에 저장해주며,
이를 통해 컴포넌트 내부 코드에서 자유롭게 사용될 수 있습니다.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Ref 사용하기
render 메소드에서 반환하는 엘리먼트에 ref 가 넘겨지면,
ref 의 current
속성을 통해 해당 노드에 접근할 수 있게 됩니다.
const node = this.myRef.current;
ref 의 값은 노드의 유형에 따라 달라집니다
- HTML 엘리먼트에 ref 어트리뷰트가 사용되면, ref 의 current 속성은 DOM 엘리먼트 객체를 가리킵니다.
- 클래스 컴포넌트에 ref 어트리뷰트가 사용되면, ref 의 current 속성은 해당 컴포넌트로부터 생성된 인스턴스를 가리킵니다.
- 함수형 컴포넌트는 인스턴스를 가질 수 없기 때문에 ref 어트리뷰트 역시 사용할 수 없습니다.
DOM 엘리먼트에 ref 사용하기
아래 코드에서는 DOM 노드를 참조하기 위해 ref 를 사용하고 있습니다:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// input DOM 엘리먼트에 접근하기 위해 ref를 만들었습니다.
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// DOM API를 사용해서 명시적으로 input에 포커스를 두는 코드입니다.
// 주의: "current" 속성을 사용해 DOM 노드에 접근하고 있습니다.
this.textInput.current.focus();
}
render() {
// <input> ref와 `textInput`이 연결되어 있다는 사실을
// React한테 알려줍니다.
return (
<div>
<input type="text" ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
Reaect 는 컴포넌트가 마운트되면 textInput 의 current 속성에 DOM 엘리먼트 객체를 할당하며,
언마운트가 되었을 때 다시 null 로 되돌릴 것입니다.
ref 의 갱신은 componentDidMount 와 componentDidUpdate 라이프사이클 훅 직전에 일어납니다.
클래스 컴포넌트에 ref 사용하기
아래 코드에서는 CustomTextInput 을 감싼 새 컴포넌트를 만들어서 마운트 되자마자 포커스가 이동하도록 했습니다.
여기서는 CustomTextInput 인스턴스에 접근하기 위해 ref 를 사용했고 focusTextInput 을 직접 호출해 주었습니다:
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focusTextInput();
}
render() {
return <CustomTextInput ref={this.textInput} />;
}
}
주의할 점은, CustomTextInput 가 클래스로 선언되었을 때만 이 코드가 동작한다는 점입니다:
class CustomTextInput extends React.Component {
// ...
}
Ref 와 함수형 컴포넌트
함수형 컴포넌트는 인스턴스를 가질 수 없기 때문에 ref 어트리뷰트 역시 사용할 수 없습니다:
function MyFunctionalComponent() {
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// 이 코드는 동작하지 않습니다!
return <MyFunctionalComponent ref={this.textInput} />;
}
}
Ref 를 사용하기 위해서는 컴포넌트를 클래스로 바꾸어주어야 합니다.
라이프사이클 메소드나 state 를 사용해야 할 때처럼 말이죠.
다만, DOM 엘리먼트가 클래스 컴포넌트의 인스턴스에 접근하기 위해
함수형 컴포넌트 안에서 ref 어트리뷰트를 사용하는 것은 얼마든지 가능합니다:
function CustomTextInput(props) {
// textInput은 반드시 여기에서 선언되어야 합니다.
let textInput = React.createRef();
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input type="text" ref={textInput} />
<input type="button" value="Focus the text input" onClick={handleClick} />
</div>
);
}