React - Ref

이소라·2022년 8월 11일
0

React

목록 보기
8/23

Ref & DOM

  • ref : render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공함



Ref의 사용 좋은 예시

  • focus, text selection, 또는 media 재생을 관리할 때
  • animation을 직접적으로 실행시킬 때
  • 3rd Party DOM 라이브러리를 React와 같이 사용할 때



Ref 사용시 주의사항

  • 선언적으로 해결할 수 있는 문제에서는 ref 사용을 지양하기
  • 애플리케이션에서 어떤 일이 일어날 때 ref를 꼭 사용해야 하는지 생각해보기
    • 어떤 컴포넌트 계층에서 상태를 소유해야 하는지 생각하기
    • 상태를 소유해야 하는 컴포넌트가 더 높은 계층일 경우, 상태를 상위 계층으로 끌어올리기
  • React 16.3 이전 버전을 사용하는 경우, React.createRef() API 대신 callback ref를 대신 사용하기



Ref 생성하기

  • Ref는 React.createRef()를 사용하여 생성하고, ref attrubute를 통해 React element에 부착됨
  • 클래스 컴포넌트의 인스턴스가 생성될 때 인스턴스의 property로 할당됨
  • 컴포넌트의 인스턴스 내 어느 곳에서도 Ref를 참고할 수 있음
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}



Ref에 접근하기

  • render 메서드 안에서 ref가 element에 전달되었을 때, 그 노드를 향한 참조는 ref의 current attribute에 담김
const node = this.myRef.current;
  • ref의 값은 노드의 유형에 따라 다름
    • ref attribute가 HTML element에 사용되었을 떄, constructor에서 React.createRef()로 생성된 ref는 자신을 전달 받은 DOM element를 current property값으로 받음
    • ref attribute가 커스텀 클래스 컴포넌트에 사용되었을 때, ref 객체는 마운트된 컴포넌트의 인스턴스를 current property값으로 받음
    • 함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref attribute를 사용할 수 없음

DOM Element에 Ref 추가하기

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // textInput DOM Element를 저장하기 위한 ref 생성
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }
  
  focusTextInput() {
    // DOM 노드를 얻기 위해 'current' property에 접근함
    this.textInput.current.focus();
  }
  
  render() {
    return (
      {/* constructor에서 생성한 'textInput'과 <input> ref를 연결함 */}
      <input 
        type="text"
        ref={this.textInput} />
      <input 
        type="button"
        ref={this.focusTextInput} />
    )
  }
}
  • React는 컴포넌트가 마운트될 때 current property에 DOM Element를 할당함
  • 컴포넌트의 마운트가 해제될 때 current 프로퍼티는 다시 null이 됨
  • componentDidMount(첫 렌더링 완료 이후 실행) 또는 componentDidUpdate(리렌더링 완료 이후 실행) 생명주기 메서드가 호출되기 전 ref가 업데이트됨

클래스 컴포넌트에 ref 사용하기

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 컴포넌트의 인스턴스가 마운트된 후 즉시 클릭되길 원한다면, CustomTextInput에 ref를 사용하여 CustomTextInput 컴포넌트의 인스턴스에 접근하고 focusTextInput 메서드를 호출할 수 있음
    • 단, CustomTextInput 컴포넌트에 ref를 사용하려면 CustomTextInput 컴포넌트가 클래스 컴포넌트이어야함

함수 컴포넌트와 Ref

  • 함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 직접 ref 속성을 사용할 수 없음
function MyFunctionComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // 이 코드는 동작하지 않음
    return (
      <MyFunctionComponent ref={this.textInput} />
    )
  }
}



부모 컴포넌트에게 자식 컴포넌트의 DOM Ref를 공개하는 방법

  • 부모 컴포넌트에서 자식 컴포넌트의 DOM 노드에 접근하기를 원할 수 있음

    • 예) 자식 컴포넌트의 DOM 노드를 focus하거나 그 DOM 노드의 크기나 위치를 측정하기 위해서
  • 자식 컴포넌트에 ref를 추가하는 것은 좋은 방법이 아님

    • 이유 1 : 자식 컴포넌트 인스턴스의 DOM 노드가 아니라 컴포넌트의 인스턴스만 가져옴
    • 이유 2 : 자식 컴포넌트가 함수 컴포넌트인 경우 동작하지 않음
  • React 16.3 이상 버젼을 사용하는 경우, ref forwarding을 사용하는 것이 좋음

    • Ref forwarding은 컴포넌트가 자식 컴포넌트의 ref를 자신의 ref로서 외부에 노출시킴
  • React 16.2나 더 낮은 버젼을 사용하는 경우, ref를 명시적으로 다른 이름의 prop으로 전달하여 사용할 수 있음

  • 자식 컴포넌트 수행의 권한이 없다면, 마지막 방법은 findeDOMNode()를 사용하는 것이나 권장하지 않으며 StrictMode에서 이 메서드는 없어졌음



Callback Ref

  • callback ref를 통해 ref가 설정되고 해제되는 상황을 세세하게 다룰 수 있음
  • callback ref는 함수를 인자로 받으며, 이 함수는 React 컴포넌트 인스턴스나 HTML DOM Element를 인자로 받음
  • ref callback을 사용하여 인스턴스의 property에 DOM 노드의 참조를 저장할 수 있음
function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el} />
    )
  }
}
  • 위 예시의 해석
    1. Parent 컴포넌트로부터 CustomTextInput 컴포넌트에 inputRef라는 이름의 prop으로 ref callback이 전달됨
    2. CustomTextInput 컴포넌트는 input의 ref attribute에 ref callback을 전달함
    3. 그 결과, Parent 컴포넌트의 this.inputElement에 CustomTextInput 컴포넌트의 input에 대응하는 DOM 노드를 설정함

Callback Ref에 대한 주의사항

  • ref callback이 인라인 함수로 정의되어 있으면, ref callback은 업데이트 과정 중 처음에는 null로 다음에는 DOM Element로 총 두 번 호출됨
    • 렌더링될 때마다 ref callback의 새로운 인스턴스가 생성됨
    • React가 이전의 ref를 제거하고 새로운 ref를 설정해야 함
    • ref callback을 클래스에 바인딩된 메서드로 정의함으로서 이러한 현상에서 벗어날 수 있음



함수 컴포넌트 내에서 Ref 사용하기

useRef() Hook을 사용하기

const refContainer = useRef(initialValue);
  • useRef
    • current property를 가진 변경 가능한 ref 객체를 반환함
    • 반환된 ref 객체는 주어진 initialValue 값으로 초기화함
    • 반환된 ref 객체는 컴포넌트의 전체 생명주기 동안 유지됨
    • uesRef는 자식 컴포넌트에 직접적으로 접근할 때 주로 사용됨
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focust();
  }
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
  • React에 ref 객체를 전달하면, React는 노드가 바뀔 때 마다 ref의 current propery로 대응되는 DOM 노드를 설정함

  • useRef()는 JavaScirpt 객체를 생성하기 때문에 변하는 값을 저장할 수 있음

    • useRef()로 ref 객체를 생성할 경우, 매 렌더링 때 마다 같은 ref 객체를 줌
    • current property이 달라지더라도, 리렌더링이 일어나지 않음
    • React가 DOM에 ref를 설정하거나 해제할 때 코드를 실행시키고 싶다면 callback ref를 대신 사용하기

Callback Ref 사용하기

const SimpleCallbackRef = () => {
    let inputRef: HTMLInputElement | null;

    const onClick = () => {
        console.log('INPUT VALUE: ', inputRef?.value);
    }

    const onFocusClick = () => {
        console.log('Focus input');
        inputRef?.focus();
    }
    console.log('Rendering')
    return (
        <div>
            <input ref={node => { inputRef = node; }} />
            <button onClick={onClick}>Log value</button>
            <button onClick={onFocusClick}>Focus on input</button>
        </div>
    );
};
  • ref callback 함수를 통해 input Element의 ref attribute을 설정할 수 있음
    • 이 함수는 DOM 노드를 인자로 받아서 inputRef 변수에 할당함
    • inputRef 변수에 DOM Element 자체가 담겨있기 때문에 current property에 접근하지 않아도 됨
    • inputRef 변수는 DOM Element나 null 둘 중 하나의 값을 가짐

Forwarding Ref 사용하기

  • React.forwardRef는 전달받은 ref attribute를 하위 트리의 다른 컴포넌트에 전달하는 React 컴포넌트를 생성함
  • React.forwardRef 사용 예
    • DOM Element로 ref를 전달하는 경우
    • 고차 컴포넌트(Higher Order Component: HOC)로 ref를 전달하는 경우
import React from 'react';

type ForwardedInputProps = {
    placeholder?: string
};

const ForwardedInput = React.forwardRef<HTMLInputElement, ForwardedInputProps>(({ placeholder }, ref) => (
    <input ref={ref} placeholder={placeholder} />
));

const SimpleForwardRef = () => {
    const inputRef = React.useRef<HTMLInputElement>(null);

    const selectText = () => {
        inputRef.current?.select();
    }

    return (
        <div>
            <ForwardedInput ref={inputRef} placeholder="Type here"/>
            <button onClick={selectText}>Select text</button>
        </div>
    );
};
  • 위 코드에서 ForwardedInput은 React.forwardRef를 사용해서 전달 받은 ref를 DOM input에 내려줌
    • 이를 통해 부모 컴포넌트에서 아래에 있는 input DOM 노드의 ref를 얻을 수 있고, inputRef의 current property를 통해서 그 노드에 접근할 수 있음



헷갈리는 개념 정리하기

State와 Ref

StateRef
공통점컴포넌트 라이프 사이클 내 유지됨, 값 변경 가능컴포넌트 라이프 사이클 내 유지됨, 값 변경 가능
차이점값 변경시 리렌더링됨값 변경시 리렌더링 안 됨

createRef와 useRef

createRefuseRef
공통점ref를 생성함ref를 생성함
차이점리렌더링시 새 ref를 생성함리렌더링시 같은 ref를 사용함

0개의 댓글