React 공식 문서 해석하며 공부하기 : Refs and DOM

배지로·2021년 8월 29일
0
post-thumbnail

해석하며 공부하는 것을 목적으로 하기 때문에 다수의 의역, 오역이 있음을 미리 밝힙니다.
원본 : https://reactjs.org/docs/refs-and-the-dom.html

Refs와 DOM

Refs는 DOM 노드나 render 메소드로 만들어진 리액트 엘리먼트들에 접근할 수 있는 방법을 제공합니다.

전형적인 리액트 데이터흐름에서는, props가 부모 컴포넌트가 자식 컴포넌트와 소통할 수 있는 유일한 방법입니다. 자식 컴포넌트를 수정하기 위해서, 새로운 props와 함께 자식 컴포넌트를 다시 렌더링해줘야 합니다. 하지만 전형적인 데이터흐름 바깥에서 자식 컴포넌트를 반드시 수정을 해줘야 하는 몇 가지 경우들도 존재합니다. 수정할 자식 컴포넌트는 리액트 컴포넌트의 인스턴스나 DOM 엘리먼트가 될 수 있습니다. 이와 같은 모든 상황에서, 리액트는 탈출구를 제시합니다.

Refs를 사용해야 하는 상황

refs를 위한 좋은 사용 예시가 있습니다 :

  • focus, 텍스트 선택, 미디어의 재생을 관리할 때

  • 애니메이션을 직접적으로 실행시킬 때

  • 써드파티(제 3자 라이브러리) DOM 라이브러리를 함께 사용할 때

    선언적으로 해결될 수 있는 것들이라면 refs를 사용하는 것을 피하세요.

    예를 들어, open(),close()메소드를 Dialog 컴포넌트에 노출시키는 대신,컴포넌트에 isOpen prop을 제공하세요.

Refs를 오용하지 않기

refs를 사용함에 있어서 어플리케이션에 "어떤 일이 일어나게"할 때 사용하는 경향이 있을 것입니다. 만약 이런 경우라면, 잠시 시간을 갖고 state가 어느 컴포넌트 계층에 위치해야하는지 신중하게 생각해보세요. 대부분, state를 소유해야 하는 적절한 위치는 더 높은 계층이라는 것이 명확할 것입니다. 이것에 대한 예시를 위해서 State 끌어올리기 가이드를 살펴보세요.

참고
아래에 있는 예시는 리액트 16.3 버전에 소개된 React.createRef() API를 사용하여 업데이트되어 있습니다. 만약 이전 버전의 리액트를 사용하고 계신다면, 대신 callback refs을 사용하는 것을 추천드립니다.

Refs 만들기

Refs는 React.createRef()를 사용하여 만들어지고 ref 속성을 통하여 리액트 앨리먼트들에서 사용됩니다. Refs는 일반적으로 컴포넌트가 생성되고 나서 인스턴스 프로퍼티에 할당되기 때문에 Refs는 컴포넌트를 통해서 참조될 수 있습니다.

class MyComponent extends React.Component{
  constructor(props){
    super(props);
    this.myRef=React.createRef();
  }
  render(){
    return <div ref={this.myRef}/>;
  }
}

Refs에 접근하기

ref가 render안 엘리먼트에 전달될 때, 노드에 대한 참조는 ref의 current 속성을 통해서 접근할 수 있습니다.

const node=this.myRef.current;

ref의 값은 노드의 타입에 따라 달라진다 :

  • HTML 엘리먼트에서 ref 속성이 사용될 때, 생성자 안에서React.createRef()로 만들어진 ref는 자신을 전달받은 DOM 엘리먼트를 current 프로퍼티 값으로 받게 됩니다.
  • ref 속성이 커스텀 클래스 컴포넌트에서 사용될 때, ref객체는 컴포넌트의 마운트된 인스턴스를 current 값으로 받게 됩니다.
  • 함수 컴포넌트는 인스턴스를 가지지 않기 때문에 함수 컴포넌트에서는 ref 속성을 사용하지 않을 것입니다.

아래의 예시에서 차이를 확인해 볼 예정입니다.

DOM 엘리먼트에 Ref 추가하기

아래의 코드는 DOM 노드에 대한 참조를 저장하기 위해서 ref를 사용합니다.

class CustomTextInput extends React.Component{
  constructor(props){
    super(props);
    //textInput DOM 엘리먼트를 저장하기 위해서 ref 만들기
    this.textInput=React.createRef();
    this.focusTextInput=this.focusTextInput.bind(this);
  }
  focusTextInput(){
    //raw DOM API를 사용하여 명시적으로 텍스트 입력에 포커스
    //참고: DOM 노드를 얻기 위해서 "current"에 접근합니다
    this.textInput.current.focus();
  }
  
  render(){
    //리액트에게 <input> ref를 생성자 안에서 만든 'textInput' ref와 연결하고 싶다고 전달
    return(
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
          />
      </div>
    );
  }
}

리액트는 컴포넌트가 마운트될 때current 프로퍼티를 DOM 엘리먼트에 할당하고, 컴포넌트가 언마운트될 때 프로퍼티에 null을 할당합니다. ref 업데이트는 생명주기 메소드componentDidMountcomponentDidUpdate 가 호출되기 전에 이루어집니다.

클래스 컴포넌트에 Ref 추가하기

만약 마운트 후에 CustomTextInput을 포장한 후 즉시 클릭되는 것을 시뮬레이션하려면, ref를 사용하여 CustomTextInput컴포넌트의 인스턴스에 접근하고 직접 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 CustomeTextInput extends React.Component{
  //...
}

Refs와 함수 컴포넌트

기본적으로, 함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에서 ref 속성을 사용할 수 없습니다.

function MyFunctionComponent(){
  return <input />;
}

class Parent extends React.Component{
  constructor(props){
    super(props);
    this.textInput=React.createRef();
  }
  
  render(){
    //이것은 작동하지 *않을* 것입니다!
    return(
      <MyFunctionComponent ref={this.textInput}/>
    );
  }
}

만약 함수 컴포넌트에 ref를 사용하고 싶다면, forwardRef를 사용하거나(아마도 useInperativeHandle와 함께) 컴포넌트를 클래스로 바꿔야 합니다.

하지만 DOM 엘리먼트나 클래스 컴포넌트를 참조하는 한, ref 속성을 함수 컴포넌트에 사용해야 합니다 :

function CustomTextInput(props){
  //textInput은 ref가 참조할 수 있도록 여기에 반드시 선언되어야 합니다
  const textInput = useRef(null);
  
  function handleClick(){
    textInput.current.focus();
  }
  
  return(
    <div>
      <input 
        type="text"
        ref={textInput}/>
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

DOM Refs를 부모 컴포넌트에 노출시키기

드문 케이스로, 부모 컴포넌트에서 자식 DOM 노드에 접근하려는 경우가 있습니다. 이것은 컴포넌트 캡슐화를 깨기 때문에 이것은 권장되지는 않지만, 때때로 focus를 유발하거나 자식 DOM 노드의 크기나 위치를 측정할 때 유용하기도 합니다.

자식 컴포넌트에 ref를 사용할 수 있지만, 이 방법은 DOM 노드보다는 컴포넌트 인스턴스를 가져온다는 점에서, 이것은 이상적인 해결책은 아닙니다. 추가적으로, 이것은 함수 컴포넌트에서 동작하지 않습니다.

리액트 16.3 이상을 사용하고 있다면, 이러한 상황에 ref forwarding를 사용하는 것을 권장합니다. Ref forwarding은 컴포넌트가 자식 컴포넌트의 ref를 자신의 ref로서 노출되는 것을 허용합니다. ref fowarding에 대한 문서에서 자식 DOM 노드를 부모 컴포넌트에 어떻게 노출시키는지에 대한 자세한 예시를 확인하실 수 있습니다.

리액트 16.2 이하를 사용하고 있다면, 또는 ref forwarding을 통한 방법보다 더 유연하게 대응하는 것이 필요하다면, 이러한 대안을 사용하거나 ref를 다르게 이름붙인 prop로 명시적으로 전달할 수 있습니다.

가능하다면 DOM 노드를 노출시키는 것을 반대하지만, 그것은 유용한 탈출구가 될 수는 있습니다. 이러한 접근 방법은 자식 컴포넌트에 코드를 추가할 수 있게 한다는 것을 참고하십시오. 자식 컴포넌트에 코드를 추가할 수 없다면 마지막 선택은 findDOMNode()를 사용하는 것이지만, 이것 장려되지 않을 뿐만 아니라StrictMode에서는 사용할 수 없습니다.

콜백 Refs

리액트는 ref의 설정,해제와 같은 세밀한 제어를 할 수 있는 "콜백 refs"라고 불리는 ref를 설정할 수 있는 또 다른 방법을 제공합니다.

createRef()로 만들어진 ref속성을 전달하는 것 대신에, 함수를 전달할 수 있습니다. 함수는 리액트 컴포넌트 인스턴스나 HTML DOM 엘리먼트를 매개변수로 받을 수 있으며, 이 매개변수는 어디서나 저장되고 접근할 수 있습니다.

아래의 예시는 인스턴스 프로퍼티에 DOM 노드의 참조를 저장하기 위해 ref 콜백을 사용하는 흔한 패턴을 보여줍니다 :

class CustomTextInput extends React.Component{
  constructor(props){
    super(props);
    
    this.textInput=null;
    
    this.setTextInputRef=element=>{
      this.textInput=element;
    };
    
    this.focusTextInput=()=>{
      //raw DOM API를 사용해서 텍스트 입력에 포커싱
      if(this.textInput) this.textInput.focus();
    };
  }
  
  render(){
    //텍스트 입력 DOM에 대한 참조를 저장하기 위해 'ref' 콜백 사용
    //인스턴스 필드에 있는 엘리먼트(예를 들어 this.textInput)
    return(
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

리액트는ref콜백을 컴포넌트가 마운트될 때 DOM 엘리먼트와 함께 호출하고, ref콜백을 컴포넌트가 언마운트될 때null과 함께 호출합니다. Refs는 componentDidMountcomponentDidUpdate가 호출되기 전에 최신의 상태로 보장되어야 합니다.

React.createRef()로 만들어진 객체 refs와 같이 컴포넌트에게 콜백 refs 또한 전달할 수 있습니다.

function CustomTextInput(props){
  return(
    <div>
      <input ref={props.inputRef}/>
    </div>
  );
}

class Parent extends React.Component{
  render(){
    return(
      <CustomTextInput
        inputRef={el=>this.inputElement=el}
      />
    );
  }
}

위의 예시에서, ParentCustomTextInputinputRef prop로 ref 콜백을 전달하고, CustomTextInput은 전달받은 함수를<input>ref 속성으로서 전달합니다. 결과적으로, Parent에 있는this.inputElementCustomTextInput<input> 엘리먼트에 상응하는 DOM 노드에 세팅될 것이다.

레거시 API : 문자열 Refs

리액트로 사용해보셨다면, ref 속성의 값이 "textInput"처럼 문자열이고, this.refs.textInput과 같은 방식으로 DOM 노드에 접근하는 구식 API에 익숙할 것입니다. 문자열 refs는 문제가 있고, 레거시 항목으로 간주되고, 향후 릴리스에서 제거될 가능성이 있기 때문에 권장되지 않습니다.

참고
현재 refs에 접근하기 위해 this.refs.textInput를 사용한다면, 대신 callback 패턴이나 createRef API를 사용하는 것을 권장합니다.

콜백 refs에 대한 경고

ref 콜백이 인라인 함수로 정의되어 있다면, ref 콜백은 업데이트 시 처음에는 null로, 그 다음에는 DOM 엘리먼트로, 총 두 번 호출됩니다. 함수의 새로운 인스턴스는 렌더링 때마다 생성되기 때문에, 리액트는 오래된 ref를 삭제하고, 새로운 ref를 세팅해줘야 합니다. 이러한 과정을 ref 콜백을 클래스 내의 바운드 메소드로 정의함으로서 피할 수 있지만, 많은 경우 이것은 문제가 되지 않습니다.

profile
웹 프론트엔드 새싹🌱

0개의 댓글