Learning React(5. React 이벤트 다루기)

1. 이벤트

01. 시작 전 준비

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React!</title>
  <!-- 리액트 라이브러리와 리액트가 DOM에 대한 작업할 때 필요한 다양한 기능을 추가 -->
  <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/[email protected]/babel.min.js"></script>
  <style>
    #container {
      padding: 50px;
      background-color: #EEE;
    }
  </style>
</head>

<body>
  <div id="container"></div>

  <script type="text/babel">
    var destination = document.querySelector("#container");
  </script>
</body>

</html>

02. UI 구현

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React!</title>
  <!-- 리액트 라이브러리와 리액트가 DOM에 대한 작업할 때 필요한 다양한 기능을 추가 -->
  <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/[email protected]/babel.min.js"></script>
  <style>
    #container {
      padding: 50px;
      background-color: #EEE;
    }
  </style>
</head>

<body>
  <div id="container"></div>

  <script type="text/babel">
    class Counter extends React.Component {
      render() {
        var textStyle = {
          fontSize : 78,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold'
        };

        return(
          <div style={textStyle}>
            {this.props.display}
          </div>
        )
      }
    }

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

        this.state = {
          count : 0
        };
      }

      render() {
        var backgroundStyle = {
          padding : 50,
          backgroundColor : '#FFC53A',
          width : 250,
          height : 100,
          borderRadius : 10,
          textAlign : 'center'
        };

        var buttonStyle = {
          fontSize : '1em',
          width : 30,
          height : 30,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold',
          lineHeight : '3px'
        };

        return (
          <div style={backgroundStyle}>
            <Counter display={this.state.count} />
            <button style={buttonStyle}>+</button>
          </div>
        );
      }
    }

    ReactDOM.render(
      <div>
        <CounterParent/>
      </div>,
      document.querySelector("#container")
    )
  </script>
</body>

</html>

03. 버튼 기능 구현

  • 버튼 클릭 이벤트 리스너를 등록
  • 클릭할 때마다 this.state.count 속성의 값을 증가시키는 핸들러를 구현한다
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React!</title>
  <!-- 리액트 라이브러리와 리액트가 DOM에 대한 작업할 때 필요한 다양한 기능을 추가 -->
  <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/[email protected]/babel.min.js"></script>
  <style>
    #container {
      padding: 50px;
      background-color: #EEE;
    }
  </style>
</head>

<body>
  <div id="container"></div>

  <script type="text/babel">
    class Counter extends React.Component {
      render() {
        var textStyle = {
          fontSize : 78,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold'
        };

        return(
          <div style={textStyle}>
            {this.props.display}
          </div>
        )
      }
    }

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

        this.state = {
          count : 0
        };

        this.increas = this.increas.bind(this);
      }

      increas(e) {
        this.setState({
          count : this.state.count + 1
        })
      }

      render() {
        var backgroundStyle = {
          padding : 50,
          backgroundColor : '#FFC53A',
          width : 250,
          height : 100,
          borderRadius : 10,
          textAlign : 'center'
        };

        var buttonStyle = {
          fontSize : '1em',
          width : 30,
          height : 30,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold',
          lineHeight : '3px'
        };

        return (
          <div style={backgroundStyle}>
            <Counter display={this.state.count} />
            <button onClick={this.increas} style={buttonStyle}>+</button>
          </div>
        );
      }
    }

    ReactDOM.render(
      <div>
        <CounterParent/>
      </div>,
      document.querySelector("#container")
    )
  </script>
</body>

</html>


04. 합성 이벤트(Synthetic Event)

  • MouseEvent나 KeyboardEvent 등과 같은 네이티브 이벤트를 받지 않으며, 항상 브라우저의 네이티브 이벤트를 래핑하는 SyntheticEvent 타입을 인자로 받는다
  • Synthetic Event를 가지고도 평범하게 DOM에서 했던 작업을 할 수 있다

    https://ko.reactjs.org/docs/events.html

-1. Synthetic Event 속성

  • boolean bubbles
  • boolean cancelable
  • DOMEventTarget currentTarget
  • boolean defaultPrevented
  • number eventPhase
  • boolean isTrusted
  • DOMEvent nativeEvent
  • void preventDefault()
  • boolean isDefaultPrevented()
  • void stopPropagation()
  • boolean isPropagationStopped()
  • DOMEventTarget target
  • number timeStamp
  • string type

-2. Mouse Synthetic Event 속성

  • boolean altKey
  • number button
  • number buttons
  • number clientX
  • number clientY
  • boolean ctrlKey
  • boolean getModifierState(key)
  • booelan metaKey
  • number pageX
  • number pageY
  • DOMEventTarget relatedTarget
  • number screenX
  • number screenY
  • boolean shiftKey

-3. Keyboard Synthetic Event 속성

  • boolean altKey
  • number charCode
  • boolean ctrlKey
  • boolean getModifierState(key)
  • string key
  • number keyCode
  • string locale
  • number location
  • boolean metaKey
  • boolean repeat
  • boolean shiftKey
  • number whitch

05. 이벤트 속성 활용하기

  • 이전 작업에서 추가로 shift키를 누른채로 플러스 버튼을 클릭하면 카운터가 10씩 증가하게 이벤트를 추가한다(boolean shiftKey)
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React!</title>
  <!-- 리액트 라이브러리와 리액트가 DOM에 대한 작업할 때 필요한 다양한 기능을 추가 -->
  <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/[email protected]/babel.min.js"></script>
  <style>
    #container {
      padding: 50px;
      background-color: #EEE;
    }
  </style>
</head>

<body>
  <div id="container"></div>

  <script type="text/babel">
    class Counter extends React.Component {
      render() {
        var textStyle = {
          fontSize : 78,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold'
        };

        return(
          <div style={textStyle}>
            {this.props.display}
          </div>
        )
      }
    }

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

        this.state = {
          count : 0
        };

        this.increas = this.increas.bind(this);
      }

      increas(e) {
        var currentCount = this.state.count;

        currentCount = (e.shiftKey)? currentCount+=10 : currentCount+=1;

        this.setState({
          count : currentCount
        })
      }

      render() {
        var backgroundStyle = {
          padding : 50,
          backgroundColor : '#FFC53A',
          width : 250,
          height : 100,
          borderRadius : 10,
          textAlign : 'center'
        };

        var buttonStyle = {
          fontSize : '1em',
          width : 30,
          height : 30,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold',
          lineHeight : '3px'
        };

        return (
          <div style={backgroundStyle}>
            <Counter display={this.state.count} />
            <button onClick={this.increas} style={buttonStyle}>+</button>
          </div>
        );
      }
    }

    ReactDOM.render(
      <div>
        <CounterParent/>
      </div>,
      document.querySelector("#container")
    )
  </script>
</body>

</html>


06. 다른 이벤트 처리 기법

-1. 컴포넌트의 이벤트는 직접 리스닝할 수 없다

_1. 컴포넌트에 직접 이벤트 전달

  • 컴포넌트는 DOM 엘리먼트를 감싸는 래퍼이기 때문에 컴포넌트는 이벤트를 직접 리스닝 할수는 없다
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React!</title>
  <!-- 리액트 라이브러리와 리액트가 DOM에 대한 작업할 때 필요한 다양한 기능을 추가 -->
  <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/[email protected]/babel.min.js"></script>
  <style>
    #container {
      padding: 50px;
      background-color: #EEE;
    }
  </style>
</head>

<body>
  <div id="container"></div>

  <script type="text/babel">
    class Counter extends React.Component {
      render() {
        var textStyle = {
          fontSize : 78,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold'
        };

        return(
          <div style={textStyle}>
            {this.props.display}
          </div>
        )
      }
    }

    class PlusButton extends React.Component {
      render () {
        var buttonStyle = {
          fontSize : '1em',
          width : 30,
          height : 30,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold',
          lineHeight : '3px'
        };
        
        return(
          <button style={buttonStyle}> + </button>
        )
      }
    }

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

        this.state = {
          count : 0
        };

        this.increas = this.increas.bind(this);
      }

      increas(e) {
        var currentCount = this.state.count;
        currentCount = (e.shiftKey)? currentCount+=10 : currentCount+=1;

        this.setState({
          count : currentCount
        })
      }

      render() {
        var backgroundStyle = {
          padding : 50,
          backgroundColor : '#FFC53A',
          width : 250,
          height : 100,
          borderRadius : 10,
          textAlign : 'center'
        };

        return (
          <div style={backgroundStyle}>
            <Counter display={this.state.count} />
            <PlusButton onClick={this.increas}/>
          </div>
        );
      }
    }



    ReactDOM.render(
      <div>
        <CounterParent/>
      </div>,
      document.querySelector("#container")
    )
  </script>
</body>

</html>



_2. 컴포넌트 내부의 DOM 엘리먼트에 이벤트 속성 전달

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React!</title>
  <!-- 리액트 라이브러리와 리액트가 DOM에 대한 작업할 때 필요한 다양한 기능을 추가 -->
  <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/[email protected]/babel.min.js"></script>
  <style>
    #container {
      padding: 50px;
      background-color: #EEE;
    }
  </style>
</head>

<body>
  <div id="container"></div>

  <script type="text/babel">
    class Counter extends React.Component {
      render() {
        var textStyle = {
          fontSize : 78,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold'
        };

        return(
          <div style={textStyle}>
            {this.props.display}
          </div>
        )
      }
    }

    class PlusButton extends React.Component {
      render () {
        var buttonStyle = {
          fontSize : '1em',
          width : 30,
          height : 30,
          fontFmail : 'sans-serif',
          color : '#333',
          fontWeight : 'bold',
          lineHeight : '3px'
        };
        
        return(
          <button onClick={this.props.clickHandler} style={buttonStyle}> + </button>
        )
      }
    }

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

        this.state = {
          count : 0
        };

        this.increas = this.increas.bind(this);
      }

      increas(e) {
        var currentCount = this.state.count;
        currentCount = (e.shiftKey)? currentCount+=10 : currentCount+=1;

        this.setState({
          count : currentCount
        })
      }

      render() {
        var backgroundStyle = {
          padding : 50,
          backgroundColor : '#FFC53A',
          width : 250,
          height : 100,
          borderRadius : 10,
          textAlign : 'center'
        };

        return (
          <div style={backgroundStyle}>
            <Counter display={this.state.count} />
            <PlusButton clickHandler={this.increas}/>
          </div>
        );
      }
    }



    ReactDOM.render(
      <div>
        <CounterParent/>
      </div>,
      document.querySelector("#container")
    )
  </script>
</body>

</html>



07. 이벤트 핸들러 내부의 this

function doSomething(e) {
	console.log(this); // button element
}

var btn = document.querySelector("button");
btn.addEventListener('click', doSomething, false);
  • 이벤트 핸들러 내부의 this는 이벤트를 발생시킨 엘리먼트를 참조한다 하지만 react에서 this는 이벤트를 발생시킨 엘리먼트의 참조가 아니다
  • 그 값은 도움이 안되는 그냥 undefined다, 지금까지 여러번 봤듯 bind 메서드를 사용해 this를 명시적으로 지정해야 하는 이유가 이 때문이다
this.increas = this.increas.bind(this);
  • 여기서 increas 이벤트 핸들러 안의 this는 이벤트를 촉발시킨 엘리먼트가 아닌 CounterParent 컴포넌트를 참조한다
  • 이는 생성자 안에서 this의 값을 컴포넌트에 바인딩 시켰기 때문이다

08. 리액트에서의 이벤트 처리는... 도대체 왜?

-1. 브라우저 호환성

  • 오늘날 브라우저 사이에서 일관되게 작동하는 기능 중 하나지만 예전 브라우저 환경으로 돌아가면 상환은 급격히 악화된다
  • 이 문제를 해결하기 위해 리액트는 모든 네이티브 이벤트를 SyntheticEvent 타입의 객체로 래핑함으로써, 호환되지 않는 환경에서도 이벤트 처리를 동일한 방법으로 할 수 있게 한다

-2. 성능 향상

  • 복잡한 UI를 갖는 앱에서 더 맘ㄶ은 이벤트 핸들러를 만들수록 앱은 더 ㅏㅁㄴ흥 메모리를 차지하게 된다
  • 이를 수동으로 조치하는 일은 어렵지 않으나 지루한 일이 될 수 있으며, 때에 따라 불가능할 수도 있다, 그리고 그런 번거로움으로 득보다 실이 더 클 수도 있다, 리액트는 이 부분을 현명하게 대처 했다
  • 리액트는 절대 이벤트 핸들러를 DOM 엘리먼트에 직접 부착하지 않는다
  • 리액트는 문서 최상위에 있는 하나의 이벤트 핸들러를 사용한다
  • 이 이벤트 핸들러는 그 모든 이벤트를 리스닝하며, 이벤트 발생 시 적합한 개별 핸들러를 호출하는 책임을 진다
  • 이는 이벤트 처리 코드를 우리가 직접 최적화하지 않아도 되게 해준다

좋은 웹페이지 즐겨찾기