[React] ref와 memo에 대해서

황준승·2021년 11월 8일
0
post-thumbnail

❗️ React Ref에 대해서...

일반적으로 React의 데이터 플로우에서 props는 부모 컴포넌트가 자식과 상호작용할 수 있는 유일한 수단입니다. 자식을 수정하려면 새로운 props를 전달하여 자식을 다시 렌더링해야합니다. 그러나, 일반적인 데이터 플로우에서 벗어나 직접 자식을 수정해야하는 경우에 사용한다.

이때 수정할 자식은 React 컴포넌트의 인스턴스일 수도 있고, DOM 엘리먼트일 수도 있습니다.

Ref를 사용해야 할 때

  1. 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때
  2. 애니메이션을 직접적으로 실행시킬 때
  3. 서드 파티 DOM 라이브러리를 React와 같이 사용할 때(... 이것은 이해가 되지 않음)
    출처 : https://ko.reactjs.org/docs/refs-and-the-dom.html

Ref를 남용하면 안된다!!

ref는 애플리케이션에 "어떤 일이 일어나게" 할 때 사용될 수도 있습니다. 그럴 때는 어느 컴포넌트 계층에서 상태를 소유해야 하는지 신중하게 생각해보기...

Componenet에서 Ref

컴포넌트가 마운트될 때 React는 current프로퍼티에 DOM 엘리먼트를 대입하고, 컴포넌트의 마운트가 해제될 때 current 프로퍼티를 다시 null로 돌려 놓습니다. ref를 수정하는 작업은 componentDidMount 또는 componentDidUpdate 생명주기 메서드가 호출되기 전 이루어집니다.

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

위의 코드를 보면 내가 커스텀한 컴포넌트에 ref값을 전달할 수 있다. 하지만 만약에 위의 예제처럼 컴포넌트를 함수형으로 만들었다면 ref 어트리뷰트를 사용할 수 없습니다.

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

...

  render() {
    // 이 코드는 동작하지 않습니다.
    return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }

따라서 아래 예제 처럼 hooks나 함수형 컴포넌트를 사용할 경우 아래 예제처럼 useRef 를 사용하는 것이 옳다.

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

useRef의 다양한 기능

  • 컴포넌트 안에서 조회 및 수정할 수 있는 변수 관리
  • useRef로 관리되는 변수는 값이 바뀌어도 컴포넌트에 리렌더링 되지 않습니다.

useRef의 자료구조?

useRef로 만들어진 객체는 React가 만든 전역 저장소에 저장되기 때문에 함수를 재호출하더라도 마지막으로 업데이트한 current값이 유지됩니다.

또한 useRef는

const nextId = { current: 4 }; // const nextId = useRef(4);

아래와 같은 객체 형태로 저장됩니다.

위와 같은 형태로 저장되기 때문에 선언한 객체의 메모리 주소가 변경되면 안됩니다. 즉, current 안에 있는 내용은 변경이 가능하겠지만(원시객체에 한해서) nextId 값에 새로운 객체의 내용을 집어넣는다면 오류가 발생할 것입니다.
=> setState처럼 리렌더링되지 않는 이유

❗️ memo

유저들은 반응이 빠른 UI를 선호한다. 이 UI 성능을 증가시키기 위해, React는 고차 컴포넌트 React.memo()를 제공한다. 렌더링 결과를 메모이징(Memoizing)함 으로써, 불필요한 리렌더링을 건너뛴다.

memo 구조(퍼온 코드)

React.memo()는 props 혹은 props 객체를 비교할 때 얕은(shallow) 비교 를 한다.

React.memo(Component, [areEqual(prevProps, nextProps)]);

areEqual(prevProps, nextProps) 함수는 prevProps와 nextProps가 같다면 true를 반환할 것이다.

언제 React.memo()를 써야 할까??

일반적으로 부모 컴포넌트에 의해 하위 컴포넌트가 같은 props로 리렌더링 될 때가 있다. 이때 사용하면 좋다.

즉, 컴포넌트가 같은 props로 자주 렌더링되거나, 무겁고 비용이 큰 연산이 있는 경우, React.memo()로 컴포넌트를 래핑할 필요가 있다.

언제 React.memo()를 사용하지 말아야 할까?

만약 위에서 언급한 상황에 일치하지 않는다면 React.memo()를 사용할 필요가 없을 가능성이 높다. 경험적으로, 성능적인 이점을 얻지 못한다면 메모이제이션을 사용하지 않는것이 좋다.

성능 관련 변경이 잘못 적용 된다면 성능이 오히려 악화될 수 있다. React.memo()를 현명하게 사용하라.

또한, 기술적으로는 가능하지만 클래스 기반의 컴퍼넌트를 React.memo()로 래핑하는것은 적절하지 않다. 클래스 기반의 컴퍼넌트에서 메모이제이션이 필요하다면 PureComponent를 확장하여 사용하거나, shouldComponentUpdate() 메서드를 구현하는 것이 적절하다.

내 생각 : 앞서 memo에 대한 경험이 적긴 하지만 아무래도 부모 컴포넌트에 대부분에 state를 선언하거나, redux를 사용할 경우는 성능 개선을 위해 memo 사용이 필연적이라고 생각한다. + props가 자주 바뀐다면 굳이 할 필요가 있을까?

리액트 구문법에는 memo와 비슷한 기능으로 shouldComponentUpdate, PureComponent(커스터마이징에 유리)가 있다.

참고자료

React memo :
https://ui.toast.com/weekly-pick/ko_20190731
https://react.vlpt.us/basic/19-React.memo.html
https://ko.reactjs.org/docs/react-api.html#reactmemo

React useRef :
https://react.vlpt.us/basic/12-variable-with-useRef.html
https://ko.reactjs.org/docs/refs-and-the-dom.html

profile
다른 사람들이 이해하기 쉽게 기록하고 공유하자!!

0개의 댓글