React를 하면서 내가 놓쳤던 것들 3편

박정훈·2022년 12월 11일
1

React

목록 보기
5/10

Ref와 DOM

Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공합니다.

일반적으로 React의 데이터 흐름에서 props는 부모 컴포넌트가 자식과 상호작용할 수 있는 유일한 수단이다. 그러나 가끔은 일반적인 흐름을 벗어나서 직접적으로 자식을 수정해야 하는 경우도 있다. 수정할 자식은 React 컴포넌트의 인스턴스일 수도 있으며, DOM 엘리먼트일 수도 있다.

Ref를 사용해야 할 때

  • 포커스, 텍스트 선택영역, 미디어의 재생 관리할 때
  • 애니메이션을 직접적으로 실행시킬 때
  • 서드 파티 DOM 라이브러리를 React와 같이 사용할 때

Ref 생성하기

React.createRef()

Ref에 접근하기

render 메서드 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 어트리뷰트에 담기게 된다.

const node = this.myRef.current;

ref의 값은 노드의 유형에 따라 다르다.

  • ref 어트리부트가 HTML 엘리먼트에 쓰였다면, React.createRef()로 생성된 ref는 자신을 전달 받은 DOM 엘리먼트를 current 프로퍼티의 값으로서 받는다.
const divRef = createRef()
return () {
  <div ref={divRef}></div>
}
  • ref 어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면, ref 객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티의 값으로서 받는다.
  • 함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없다. ❌

DOM 엘리먼트에 Ref사용하기

컴포넌트가 마운트될 때 React는 current 프로퍼티에 DOM 엘리먼트를 대입한다.
컴포넌트의 마운트가 해제될 때 current 프로퍼티를 다시 null로 돌려 놓는다.

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

  focusTextInput = () => {
    this.textInput.current.focus();
  };

  render() {
    return (
      <div>
        <input type="text" ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

export default CustomTextInput;

focus input

클릭하면 focus 한다.

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

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

  componentDidMount() {
    // CustomTextInput 컴포넌트의 인스턴스에 접근, 직접 focusTextInput 메서드를 호출한다.
    this.textInput.current.focusTextInput();
  }

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

focus input

mount되고 바로 focus 한다.

위 코드는 반드시 클래스 컴포넌트여야 한다.

부모 컴포넌트에게 DOM ref를 공개하기

부모 컴포넌트에서 자식 컴포넌트의 DOM 노드에 접근하려 하는 경우도 있다. 이는 컴포넌트의 캡슐화를 파괴하기에 권장되지는 않지만 가끔가다 자식 컴포넌트의 DOM 노드를 포커스하는 일이나, 크기 또는 위치를 계산하는 일 등을 할 때에는 효과적인 방법이 될 수 있다.

React 16.3 이후 버전을 사용한다면 forwardRef가 효과적인 방법이 될 수 있다.
React 16.2 이전 버전을 사용한다면 ref props로 넘기기를 확인하자.

콜백 ref

콜백 ref는 ref 어트리뷰트에 React.createRef()를 통해 생성된 ref 대신, 함수를 전달한다!

class CustomTextInput extends Component {
  constructor(props) {
    super(props);
    
    this.textInput = null;

    this.setTextInputRef = (e) => (this.textInput = e);

    this.focusTextInput = () => this.textInput && this.textInput.focus();
  }

  componentDidMount = () => this.focusTextInput();

  render() {
    return (
      <div>
        <input type="text" ref={this.setTextInputRef} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

Render Props

React 컴포넌트 간에 코드를 공유하기 위해 함수 props를 이용하는 간단한 테크닉이다.

횡단 관심사(Cross-Cutting Concerns)를 위한 render props 사용법

import React from "react";

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <p style={{ position: "absolute", left: mouse.x, top: mouse.y }}>
        고양이
      </p>
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);

    this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  };

  render() {
    return (
      <div style={{ height: "100vh" }} onMouseMove={this.handleMouseMove}>
        {/*
            <Mouse>가 무엇을 렌더링하는지에 대해 명확히 코드로 표기하는 대신,
            `render` prop을 사용하여 무엇을 렌더링할지 동적으로 결정할 수 있다!
          */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={(mouse) => <Cat mouse={mouse} />} />
      </div>
    );
  }
}

export default MouseTracker;

render에 다른 동물 컴포넌트를 넘겨주기만 하면 된다.

render 이외의 Props 사용법

render props pattern으로 불린다고 해서 prop name이 render일 필요는 없다.

class Mouse extends React.Component {
  constructor(props) {
    super(props);

    this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  };

  render() {
    return (
      <div style={{ height: "100vh" }} onMouseMove={this.handleMouseMove}>
        {this.props.children(this.state)}
      </div>
    );
  }
}

// ...
<Mouse>
  {(mouse) => (
    <p style={{ position: "absolute", left: mouse.x, top: mouse.y }}>
      고양이
    </p>
  )}
</Mouse>

다만 ... 이런 테크닉은 자주 사용되지 않기에 children은 함수 타입을 가지도록 propsTypes를 지정하자.

React.PropTypes는 React v15.5부터 다른 패키지로 이동하였습니다. 대신 prop-types 라이브러리를 사용하시길 바랍니다. prop-types

이미 존재하는 CRA 프로젝트에 TypeScript 추가하기

Adding TypeScript

리액트는 두가지 단계로 동작한다.

  • 렌더링 단계
    • 특정 환경(DOM과 같은)에 어떤 변화가 필요한 지 결정하는 단계이다. 이 과정에서 React는 render를 호출해서 이전 렌더와 결과값을 비교한다.
  • 커밋 단계
    • React가 변경 사항을 반영하는 단계이다(React DOM의 경우 React가 DOM 노드를 추가, 변경 및 제거하는 단계). 이 단계에서 React는 componentDidMount 나 componentDidUpdate 같은 생명주기 메서드를 호출한다.

와...

이렇게.. 주요 개념과 고급 안내서까지 정독했다.🤸‍♂️ 생각보다 몰랐던 점이 많았다는게 놀랍기도 하면서 당연하기도...(읽어본적이 없으니)
API참고서와 HOOK이 남았다. 이 부분까지 몰랐던 점을 적을지 말지는 고민을 해봐야겠다.. 슬쩍 봤는데 벌써 모르는게 나오긴 하는것이 고놈 참!
아무튼 재밌었음!

profile
그냥 개인적으로 공부한 글들에 불과

0개의 댓글