본문 바로가기
dev/React

[React] DOM 엘리먼트 접근

by dev_Step 2022. 12. 1.

DOM 엘리먼트 접근 

 - HTML 엘리먼트의 속성이나 메소드에 직접 접근해야할 일이 생길것이다. 자바스크립트의 DOM API를 통해 직접 HTML 엘리먼트를 다루는 일이 '리액트 방식'으로 할 때보다 더 쉬운 경우가 많기 때문이다.

 - 리액트는 HTML 엘리먼트에 대한 DOM API에 접근할 수 있게 하는, ref라는 도구를 제공한다.

 - 또한 페이지 안의 어떤 HTML 엘리먼트든 렌더링할 수 있게 하는 포털이라는 기능도 제공한다.

 

-- 예제를 통해 한번 알아보도록 하자

 >> ref 와의 첫만남

  - 대부분의 경우 render 메소드 안에서 HTML을 닮은 JSX 코드를 작성해왔다. 리액트는 DOM안에서의 최종 HTML 엘리먼트와 JSX 사이를 연결해주는 ref라는 기능을 제공한다.

 - 위의 render 메서를 한번 봐보자

 render(){
            var squareStyle = {
                backgroundColor : this.state.bgColor        
            }

            return (
                <div className="colorArea">
                    <div style={squareStyle} className="colorSquare"></div>

                    <form onSubmit={this.setNewColor}>
                        <input onChange={this.colorValue} placeholder="Enter a color value"></input>
                        <button type="submit">go</button>
                    </form>
                </div>
            )
        }

 - 자바스크립트를 사용해 API를 호출할수 있게, Input 엘리먼트의 DOM에 접근 하는 작업을 해보자

 - 접근하려는 HTML엘리먼트에 ref라는 속성을 설정할 수 있다

render(){
            var squareStyle = {
                backgroundColor : this.state.bgColor        
            }

            return (
                <div className="colorArea">
                    <div style={squareStyle} className="colorSquare"></div>

                    <form onSubmit={this.setNewColor}>
                        <input onChange={this.colorValue}
                        				ref= {}
                                         placeholder="Enter a color value">
                        </input>
                        <button type="submit">go</button>
                    </form>
                </div>
            )
        }

 -  현재 ref의 속성 값은 비어있다

 - 보통은 현재 컴포넌트가 마운트되면 자동으로 호출될 자바스크립트 콜백 함수를 ref 속성 값으로 설정한다.

 - ref 속성 값으로 단순히 DOM 엘리먼트의 참조를 저장하는 자바스크립트 함수를 지정한다면 다음과 같을 것이다.

render(){
            var squareStyle = {
                backgroundColor : this.state.bgColor        
            }

			var self = this;
	
			
            return (
                <div className="colorArea">
                    <div style={squareStyle} className="colorSquare"></div>

                    <form onSubmit={this.setNewColor}>
                        <input onChange={this.colorValue}
                        	
                            ref = {
                            	function(el) {
                                	self._input = el
                                }
                            
                            }
                        
                        placeholder="Enter a color value"></input>
                        <button type="submit">go</button>
                    </form>
                </div>
            )
        }

 

-  이 익명함수는 컴포넌트가 마운트되면 호출되며, 최종 HTML DOM 엘리먼트에 대한 참조 인자로 받는다.

function(el) {
	self._input = el
}

- 이 인자를 el 이라고 했지만 어떤 이름이든 상관 없다.

- 이 함수는 단순히 _input이라는 커스텀 속성에 DOM 엘리먼트의 값을 지정하는 일을 한다.

- 또한 컴포넌트에 _input 속성을 만들기 위해 self 변수를 사용해 클로저를 만들었다.

 

- 지금의 목표는 폼이 제출되면  input 엘리먼트의 콘텐츠를 지우고 포커스를 갖게 하는 것이다

- 이를 위한 코드를 setNewColor에 넣기로하고 다음 내용을 추가하자

setNewColor(e){
	this.setState({
    	bgColor : this.state.color
    });
    
    this._input.focus();			--추가
    this._input.input.value= ""; 	--추가
    
    e.preventDefault();
}

- this._input.value = "" 를 호출함으로써 input 엘리먼트에 입력됬던 내용은 삭제될 것이다.

- 또한 this._input.focus() 를 호출해 input 엘리먼트로 포커스가 가게 했다. 

- ref 와 관련된 작업은 이 두라인이 전부다.

 

** ref 와 관련하여 콜백함수를 다룰때 화살표 함수를 사용하면 작업이 더 편해진다.

<input 
	ref = {
    	function(el) {
        	self._input = el;
        }
    }
>
</input>

또한 self 변수를 this로 초기화하여 컴포넌트에  _input 속성이 확실히 만들어지게 했다.
근데 여기서 화살표 함수를 사용한다면 더 깔끔하게 만들수 있다

<input
	ref = {
    	(el) => this._input = el
    }>
 </input>
 
 - 화살표 함수는 컨텍스트를 다루기 때문에 함수 안에서 this를 사용해 컨포넌트를 참조할 수 있게 
 됬다. 따라서 self와 같은 외부 변수를 만들 필요가 없어졌다.

 

 

>> 포털 사용하기

 - 지금 까지는 단일 컴포넌트든 복수 컴포넌트의 조합이든 오직 JSX가 만드는 결과로서 HTML을 다뤘다. 이는 부모 컴포넌트가 강제하는 DOM 계층 구조로 제한된다는 의미이다. 따라서 다른 위치의 DOM엘리먼트에 임의로 접근하는 일은 불가능해 보인다

 - 하지만 JSX는 페이지 어느 곳이든 DOM엘리먼트로 렌더링 시킬 수 있다. 바로 포털이라는 기능이다.

 - 포털의 사용방법은 ReactDOM.render 메소드와 매우 유사하다. 즉 렌더링할 JSX를 지정하고, 렌더링해 보낼 대상 DOM 엘리먼트를 지정하면 된다.

 - 예제로 돌아가서 container div 엘리먼트와 동등한 수준으로, 즉 형제 h1 엘리먼트를 추가해 보자

 

 - 현재 보여주고 있는 컬러의 이름으로 h1 엘리먼트의 값이 바뀌게 해보자 핵심은 container div 와 h1이 형제 엘리먼트라는 점이다.

 

 - ColorLabel 이라는 컴포넌트를 인스턴스화 하고, state.bgColor 속성을 color 속성 값으로 설정했다.

 - 또한 ColorLabel이라는 컴포넌트를 정의해 주었다.

 - 여기서 중요한 점은 return 구문에서 ReactDOM.createPortal()으 호출 결과를 리턴하는 부분이다.

 - ReactDOM.createPortal()은 두개의 인자를 받는다

   1) 출력될 내용의 JSX 이며 -> 단지 문자하나와 속성으로부터의 컬러 값이 전부다.

   2) 둘쨰는 그 JSX를 출력시킬 대상 DOM 엘리먼트이다.   -> 모든 내용을 출력시킬 DOM 엘리먼트는 h1엘리 먼트이다.

 - 강조할 사항은 h1 엘리먼트가 container div를 출력하는 앱의 범위 밖에 있다는 점이다.

 - 즉 포털을 사용함으로써 부모와 자식으로 이뤄진 기존 계층 구조에 갇히지 않고 페이지의 어느 엘리먼트든 직접 접근이 가능하게 된다는 얘기이다.

 

-- 최종코드

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Lifecycle Methods</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<style>
    #container {
      padding : 50px;
      background-color : #fff;
    }
    .colorSquare {
        box-shadow : 0px 0px 25px 0px #333;
        width : 242px;
        height : 242px;
        margin-bottom : 15px;
    }
    .colorArea input{
        padding : 10px;
        font-size : 16px;
        border : 2px solid #ccc;
    }
    .colorArea button{
        padding : 10px;
        font-size : 16px;
        margin : 10px;
        background-color : #666;
        color : #fff;
        border : 2px solid #666;
    }
    .colorArea button:hover{
        background-color : #111;
        border-color : #111;
        cursor : pointer;
    }
    #colorHeading {
        padding : 0;
        margin : 50px;
        margin-bottom : -20px;
        font-family: sans-serif;

    }
  </style>
</head>
<body>

   <h1 id="colorHeading"> Colorizer</h1> 
  <div id="container"></div>
  <script type="text/babel">

    var heading = document.querySelector("#colorHeading");
    
    class ColorLabel extends React.Component{   
        render(){
            return ReactDOM.createPortal(
                    ": " + this.props.color,
                    heading
            );
            
        }
    }
    class Colorizer extends React.Component{
        constructor(props){
            super(props);

            this.state ={
                color : "",
                bgColor : "white"
            };

            this.colorValue = this.colorValue.bind(this);
            this.setNewColor = this.setNewColor.bind(this);
        }

        colorValue(e){
            this.setState({
                color : e.target.value
            });
        }

        setNewColor(e){
            this.setState({
                bgColor : this.state.color
            })
            
            this._input.focus();
            this._input.value="";

            e.preventDefault();
        }

        render(){
            var squareStyle = {
                backgroundColor : this.state.bgColor        
            }

            return (
                <div className="colorArea">
                    <div style={squareStyle} className="colorSquare"></div>

                    <form onSubmit={this.setNewColor}>
                        <input onChange={this.colorValue} 
                                ref = {
                                    (el) => this._input = el
                                }
                                placeholder="Enter a color value"></input>
                        <button type="submit">go</button>
                    </form>
                    <ColorLabel color={this.state.bgColor}/>
                </div>
            )
        }
 
    }

    ReactDOM.render(
        <div>
            <Colorizer />
        </div>
        ,document.querySelector("#container")
    );
 

    
  </script>
</body>

</html>

'dev > React' 카테고리의 다른 글

[React18] 엘리먼트 렌더링  (0) 2022.12.11
[React] 외부 데이터 사용  (0) 2022.12.02
[REACT] 컴포넌트 생명주기  (0) 2022.12.01
[React] 이벤트  (0) 2022.11.30
[React] 데이터에서 UI로  (0) 2022.11.30