[React] Ref 와 DOM

Ref 와 DOM


Ref 는 render 메소드에서 생성된 DOM 노드 혹은 React 엘리먼트 객체에 접근할 수 있는 방법을 제공합니다.

전형적인 React 데이터 흐름에서는, 부모 컴포넌트에서 자식 엘리먼트를 조작하기 위해 props 만을 사용합니다.
즉, 자식 엘리먼트를 수정하기 위해 새 prop 을 가지고 다시 렌더링을 해줍니다.
하지만 가끔은 전형적인 데이터 흐름 밖에서 자식을 명령형으로 변경해야 할 필요가 있습니다.
여기서 변경될 자식이란 React 컴포넌트의 인스턴스일 수도 있고, DOM 엘리먼트일 수도 있습니다.
React 는 양쪽 경우 모두를 위한 비상구를 제공합니다.

언제 ref 를 사용해야 하나요?

Ref 의 바람직한 사용 사례로 다음과 같은 것을 들 수 있습니다

선언적으로 할 수 있는 작업에 대해서는 ref 의 사용을 피하세요.

예를 들어, Dialog 컴포넌트에 open()과 close()라는 메소드를 두는 대신 isOpen 과 같은 prop 을 넘겨주세요.

Ref 의 남용은 금물입니다

여러분의 앱에 “어떤 일이 일어나게”하기 위해 ref 를 사용하는 쪽으로 마음이 기울 수 있습니다.
이 때에는, 잠시 작업을 멈추고 앱의 상태를 컴포넌트 계층의 어떤 부분에서 소유해야 하는지를 다시 한 번 생각해보세요.
많은 경우, 상태를 “소유”해야 할 적절한 장소는 계층의 상위에 있는 컴포넌트라는 결론이 날 것입니다.


Ref 생성하기

RefReact.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 의 값은 노드의 유형에 따라 달라집니다


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>
  );
}