React.js - DOM 엘리먼트 조작(ref, portal)

Gyu·2022년 4월 13일
0

React.js

목록 보기
8/20
post-thumbnail
post-custom-banner

React에서 DOM 엘리먼트에 접근하기

  • 자바스크릅트에서 제공하는 API를 이용해 DOM 조작을 하는 것이 리액트 방식을 이용하는 것보다 더 쉬운 경우가 있다.
  • 이를 위해 리액트는 HTML 엘리먼트에 대한 DOM API에 접근할 수 있게하는 ref라는 도구를 제공한다.
  • 또한 페이지 안의 어떤 HTML 엘리먼트든 랜더링 할 수 있는 포털(portal)이라는 기능도 제공한다.

ref 사용하기

  • JSX는 단순히 DOM이 어떻게 보여야 할지를 기술한다. 그러나 실제 HTML을 대변하는 것은 아니다. 때문에 이를 보완하기 위해 리액트는 최종 렌더링된 HTML 엘리먼트와 JSX 사이를 연결해주는 ref(reference의 약자)라는 기능을 제공한다.
  • ref 속성 추가
    // 일반 함수 사용
    
    // 익명함수 내부에 쓰기위해 this를 변수에 할당
    const self = this;
    
    return (
    	<Element ref={
    			function(el) {
    				self._input = el;
    			}
    		}
    	/>
    );
    
    // 화살표 함수 사용
    return (
    	<Element ref={
    			(el) => {
    				this._input = el;
    			}
    		}
    	/>
    );
    • 해당 컴포넌트가 마운트 되면 컴포넌트 내부 어디서든 self._input 혹은 this._input를 사용해 input 엘리먼트를 나타내는 HTML에 접근할 수 있다.
    • JSX 내부에 작성한 익명함수는 컴포넌트가 마운트될 때 호출되며, 최종 HTML DOM 엘리먼트에 대한 참조를 매개변수로 받는다. 위 예제에서는 el이라고 했지만 매개변수 이름은 개발자가 변경 가능하다.
    • 익명함수는 단순히 컴포넌트에 _input이라는 커스텀 속성을 만들고 그 속성에 DOM 엘리먼트의 값을 지정한다.
  • ref 예제
    class Welcome extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                name: ''
            }
            this.showWelcomeMessage = this.showWelcomeMessage.bind(this);
        }
    
        showWelcomeMessage() {
            alert(`환영합니다. ${this.state.name}!!`);
            this.name_input.value = '';
            this.name_input.focus();
        }
    
        render() {
            return(
                <div>
                    <input 
                        type="text" 
                        placeholder="이름을 입력해주세요" 
                        ref={
                            (el) => {
                                this.name_input = el;
                            }
                        }
                    />
                    <button 
                        type="button"
                        onClick={this.showWelcomeMessage}
                    >
                        클릭
                    </button>
                </div>
            );
        }
    }
    
    ReactDOM.render(
        <Welcome/>,
        document.getElementById('app')
    )

16.3에서 ref 사용하기

  • 리액트 16.3v에서는 위 방식이 아닌 React.createRef()를 통해 ref를 더 손쉽게 사용할 수 있다.
    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.myRef = React.createRef(); // ref 생성
      }
      render() {
        return <div ref={this.myRef} />; // ref 어트리뷰트를 통해 React 엘리먼트에 부착
      }
    }

ref에 접근하기

const node = this.myRef.current;
  • render 메서드 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 프로퍼티에 담기게된다.
  • ref의 값은 노드의 유형에 따라 다르다.
    • ref 어트리뷰트가 HTML 엘리먼트에 쓰였다면, 생성자에서 React.createRef()로 생성된 ref는 current 프로퍼티의 값으로 자신을 전달받은 DOM 엘리먼트를 받는다.
    • ref 어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면, ref 객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티의 값으로 받습니다.
    • 함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 프로퍼티를 사용할 수 없다.

포털 사용하기

  • 리액트 컴포넌트의 랜더링 메서드가 반환하는 엘리먼트는 부모 노드에서 가장 가까운 자식 요소로 DOM에 마운트 된다. 즉 자식 요소는(컴포넌트, 엘리먼트 등) 부모 컴포넌트의 DOM 내부에 렌더링 된다
  • 포탈을 사용하면 자식 요소를 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 렌더링 할 수 있다. 즉 자식 요소를 개발자가 원하는 위치에 랜더링 할 수 있다.
  • 포탈을 사용하면 개발자가 작성한 JSX 계층구조에 종속되지 않으면서 컴포넌트를 렌더링 할 수 있다.

포탈 사용법

  • ReactDOM.createPortal(child, container)
    • child : 엘리먼트, 문자열, 혹은 fragment와 같은 어떤 종류이든 렌더링할 수 있는 React 자식
    • container : DOM 엘리먼트
    • 위 두 매개변수를 전달받아 child를 container에 렌더링하는 메서드
  • 예1
    // 일반 코드
    render() {
      // React는 새로운 div를 마운트하고 그 안에 자식을 렌더링한다.
      return (
        <div>
          {this.props.children}
        </div>
      );
    }
    
    // portal 사용 코드
    render() {
      // React는 새로운 div를 생성하지 *않고* `domNode` 안에 자식을 렌더링한다.
      // `domNode`는 DOM 노드라면 어떠한 것이든 유효하고, 그것은 DOM 내부의 어디에 있든지 상관없다.
      return ReactDOM.createPortal(
        this.props.children,
        domNode
      );
    }
  • 예2
    class Label extends React.Component {
        render() {
            return ReactDOM.createPortal(
                ` : Portal Test`,
                document.getElementById('heading')
            );
        }
    }
    
    class Welcome extends React.Component {
        constructor(props) {
            super(props);
            this.showWelcomeMessage = this.showWelcomeMessage.bind(this);
        }
    
        showWelcomeMessage() {
            alert(`환영합니다. ${this.name_input.value}!!`);
            this.name_input.value = '';
            this.name_input.focus();
        }
    
        render() {
            return(
                <div>
                    <input 
                        type="text" 
                        placeholder="이름을 입력해주세요" 
                        ref={
                            (el) => {
                                this.name_input = el;
                            }
                        }
                    />
                    <button 
                        type="button"
                        onClick={this.showWelcomeMessage}
                    >
                        클릭
                    </button>
                    <Label/>
                </div>
            );
        }
    }
    
    ReactDOM.render(
        <Welcome/>,
        document.getElementById('app')
    )
profile
애기 프론트 엔드 개발자
post-custom-banner

0개의 댓글