본문 바로가기

[React] React의 이벤트 처리(Event handling) : state 다양한 응용

[React] React의 이벤트 처리(Event handling) : state 다양한 응용

React의 이벤트 처리(이벤트 핸들링; Event handling) 방식은 DOM의 이벤트 처리 방식과 유사하다. 단, 몇 가지 문법 차이가 있다.

  • React 에서 이벤트는 소문자 대신 카멜 케이스(camelCase) 를 사용한다.
  • JSX를 사용하여 문자열이 아닌 함수로 이벤트 처리 함수(이벤트 핸들러; Event handler)를 전달한다.
  • 이벤트 핸들러는 props로 전달할 수 있다.

 

예를 들어 HTML에서 이벤트 처리 방식이 아래와 같다면,

<button onclick="handleEvent()">Event</button>

 

React의 이벤트 처리 방식은 아래와 같다.

<button onClick={handleEvent}>Event</button>

 

자주 사용되는 이벤트 처리에 대해 알아보자.

 


 

onChange

 

<input> <textarea> <select> 와 같은 폼(Form) 엘리먼트는 사용자의 입력값을 제어하는 데 사용된다.

 

  • React 에서는 이러한 변경될 수 있는 입력값을 일반적으로 컴포넌트의 state 로 관리하고 업데이트한다.
  • onChange 이벤트가 발생하면 e.target.value 를 통해 이벤트 객체에 담겨있는 input 값을 읽어올 수 있다.

 

아래 코드에 대한 설명
  • 컴포넌트 return 문 안의 input 태그에 value 와 onChange 를 넣어주었다.
  • onChange 는 input 의 텍스트가 바뀔 때마다 발생하는 이벤트이다.
  • 이벤트가 발생하면 handleChange 함수가 작동하며, 이벤트 객체에 담긴 input 값을 setState 를 통해 새로운 state 로 갱신한다.
function NameForm() {
  const [name, setName] = useState("");

  const handleChange = (e) => {
    setName(e.target.value);
  }

  return (
    <div>
      <input type="text" value={name} onChange={handleChange}></input>
      <h1>{name}</h1>
    </div>
  )
};

 


 

onClick

 

onClick 이벤트는 말 그대로 사용자가 클릭이라는 행동을 하였을 때 발생하는 이벤트이다. 버튼이나 <a> tag 를 통한 링크 이동 등과 같이 주로 사용자의 행동에 따라 애플리케이션이 반응해야 할 때 자주 사용한다.

 

그럼 위의 onChange 예시에 버튼을 추가하여 버튼 클릭 시 input tag 에 입력한 이름이 alert을 통해 알림 창이 팝업 되도록 코드를 추가해 보겠다. 아래의 코드는 잘못된 예시이다.

function NameForm() {
  const [name, setName] = useState("");

  const handleChange = (e) => {
    setName(e.target.value);
  }

  return (							
    <div>
      <input type="text" value={name} onChange={handleChange}></input>
      <button onClick={alert(name)}>Button</button> // 🟣 틀린 예시이다.
      <h1>{name}</h1>
    </div>
  );
};

 

문제

  • 위와 같이 onClick 이벤트에 alert(name) 함수를 바로 호출하면 input 박스에 'ㄱ' 입력이 시작되고 컴포넌트가 렌더링 될 때 함수 자체가 아닌 함수 호출의 결과가 onClick 에 적용된다.
  • 버튼을 클릭할 때가 아닌, 컴포넌트가 렌더링 될 때에 alert 이 실행되고 따라서 그 결과인 undefined (함수는 리턴 값이 없을 때 undefined 를 반환)가 onClick 에 적용되어 클릭했을 때 아무런 결과도 일어나지 않는다.
  • 렌더링할 때마다 해당 함수가 호출되기 때문에 성능 이슈가 우려된다. [공식 문서]
[정리]
onClick={alert(name)} (다른 예시: onClick={handleIncrease()} 등)와 같이 진행하게 된다면, 리턴 값이 onClick으로 전달되어서 함수 자체가 전달되는 것이 아니라, 함수의 결괏값이 전달된다.  

** 즉, 기존 자바스크립트의 방법과 다르다 (아래 예시는 버튼을 클릭 할때 '클릭'이라고 적힌 안내창이 뜬다.)
<button onclick="alert(textContent)">클릭</button>

 

해결방법

  • 따라서 onClick 이벤트에 함수를 전달할 때는 함수를 호출하는 것이 아니라 아래와 같이 리턴문 안에서 함수를 정의하거나 리턴문 외부에서 함수를 정의 후 이벤트에 함수 자체를 전달해야 한다.
[안 되는 사례 하나 더 정리]

<button onClick= {() => plusNumber}> 버튼입니다 </button>​

익명함수가 화살표 함수 형태로 배치되어 있다. 이에 따라, Plus 버튼을 클릭 했을 때  plusNumber 함수가 리턴될 뿐 호출되지는 않는다.

 

오키.. 알았어.. 알겠다구

{} 안에 함수를 정의(그 안의 함수는 호출)하거나 함수 자체를 전달해야 한다.
// 함수 정의하기

return (
  <div>
	...
    <button onClick={() => alert(name)}>Button</button>
	...
  </div>
  );
};

// 함수 자체를 전달하기

const handleClick = () => {
  alert(name);
};

return (
  <div>
      ...
    <button onClick={handleClick}>Button</button>
      ...
  </div>
  );
};

그냥 쉽게 onClick HSX는 이벤트가 일어나면 onClick = {} 속 함수를 (), 즉 호출하기 시작한다고 생각하면 된다.

 

 


 

Action item 1 : <select>

 

 

<select> 태그는 Form 엘리먼트의 하나로 사용자의 입력값을 제어하는 데 주로 사용된다. select tag 는 사용자가 drop down 목록을 열어 그중 한 가지 옵션을 선택하면, 선택된 옵션이 state 변수에 갱신된다. 즉, React에서는 이러한 변경될 수 있는 입력값을 일반적으로 컴포넌트의 state로 관리하고 업데이트를 한다.

 

미리 알아야 하는 select 태그
: 하위 태그로 option이 있으며 이 option을 사용자가 선택할 때 해당 value 값이 저장된다.
<select>
    <option value="americano">아메리카노</option>
    <option value="caffe latte">카페라테</option>
    <option value="cafe au lait" selected>카페오레</option>
    <option value="espresso">에스프레소</option>
</select>

 

select 태그와 관련된 state 값 갱신하기
import React, { useState } from "react";
import "./styles.css";

function SelectExample() {
  const [choice, setChoice] = useState("apple"); // 여기서 state 변수명은 choice

  const fruits = ["apple", "orange", "pineapple", "strawberry", "grape"];
  const options = fruits.map((fruit, idx) => {				    
    return <option key={idx} value={fruit}>{fruit}</option>;   // idx를 키 값으로 줌
  });
  console.log(choice);
  
  // 🟣 (3) 이때 핸들러 함수의 역할은 선택된 과일로 state를 갱신시켜주는 일을 한다.
  // 🟣 (4) 핸들러 함수는 onChange 이벤트의 event 객체를 인자로 받아 사용할 수 있다.
  // 🟣 (5) 여기서 event.target.value가 바로 해당 옵션의 값이다. 
  //    (이벤트 객체에는 발생한 이벤트에 대한 모든 정보가 담겨있고, 그중
  //    target 속성에는 이벤트가 발생한 엘리먼트에 대한 정보가 담겨있다)
  const handleFruit = (event) => {
  // 🟣 (6)setChoice(state 갱신함수)가 호출되어 해당 옵션의 값이 인자로 전달되면
  //       state의 변수명인 choice의 값이 바뀐다.
 
    setChoice(event.target.value)  
  };

  return (
    <div className="App">

      // 🟣 (1) select 태그의 옵션을 선택하면 onChange 이벤트가 발생한다.
      // 🟣 (2) 그에 따라 해당 이벤트에 할당되어 있는 handleFruit 핸들러 함수가 호출된다.
      
      // options의 첫 번째 요소는 <option value={apple}>{apple}</option>; 이런식임 
       <select onChange={handleFruit}>{options}</select>

      // 🟣 (7)react는 페이지를 렌더링하고 <h3> 태그의 내용이 event.target.value의 값으로 바뀐다.
      <h3>You choose "{choice}"</h3>
    </div>
  );
}

export default SelectExample;

 


 

Action item 2 : Pop up

 

 

Pop up 역시 Pop up 의 open 과 close 를 state 를 통해 관리할 수 있다.

import React, { useState } from "react";
import "./styles.css";

function App() {
  const [showPopup, setShowPopup] = useState(false); // state 변수명 showPopup

  const togglePopup = (event) => {
    if(showPopup===false){	    // 🟣 (2) 초기값이 false였기에 버튼 클릭에 따라
      setShowPopup(true)		// 🟣 (3) showPopup의 값이 true가 된다.	
      } else(setShowPopup(false)) // 🟣 (6) showPopup 값이 true이기에 else에서 false로 바뀐다.
      };

  return (
    <div className="App">
      <h1>Pop Up</h1>
       
      <button className="open"
      onClick = {togglePopup} >Open me</button> // 🟣 (1) 처음 버튼 클릭시 함수가 호출된다

      {showPopup ? (				     // 🟣 (4) showPopup 값이 true면 "popup" div 생성
        <div className="popup">
          <div className="popup_inner">
            <h2>Success!</h2>
            <button className="close" onClick={togglePopup}> // 🟣 (5) 닫기위한 버튼 클릭
              Close me		                                //  똑같은 함수가 호출된다.
            </button>
          </div>
        </div>
      ) : null}					// 🟣 (7) showPopup 값이 false면 null (아무 것도 없음)
    </div>
  );
}

export default App;
  • 버튼 클릭에 따라 Pop up이 열고 닫히기 위해서는 버튼을 눌렀을 때 togglePopup 함수가 호출 되어야 한다.
  • 따라서 onClick 이벤트 처리로 togglePopup 함수를 넣어줘야 한다. 
  • 또한 버튼 클릭에 따라 state 저장변수인 showPopup의 상태가 바뀌어야 한다. state 저장변수가 갱신되기 위해서는 state 갱신함수인 setShowPopup 을 호출해야 한다.
  • showPopup의 초깃값이 false 이므로 true로 바꿔 주기 위해 setShowPopup 에 true 를 넣어 호출한다.
  • 다시 Pop up 창을 닫기 위해서는 showPopup 을 false 로 바꿔주기 위해 setShowPopup 에 false 를 넣어 호출한다.  

 


 

Controlled Component

 

간단한 글을 쓰면 전체 글이 업데이트가 되는 등 간단한 기능 위주로 트위터와 비슷하게 만들어보자고 가정해보자.

입력폼은 다음과 같이 구상했다.

 

변하는 값? 변하지 않는 값?

 

변하지 않는 

  • 트윗을 받는 사람 (팔로워 등)

 

변할 수 있는 값, 상태 (state)

  • 트윗을 보내는 사람 (누가 작성하는가에 따라 변경) -> username
  • 트윗의 내용 -> tweet

 

React에서는 이렇게 상태에 해당하는 데이터를 state로 따로 관리하고 싶어 한다. 이렇게 React가 state를 통제할 수 있는 컴포넌트를 Controlled Component라고 한다. (Hooks로 Controlled Component 구현에 대해서 더 자세히 알고 싶으시면 공식 문서의 해당 링크를 참고)

 

React가 state를 통제하는 방법

  •  input에 값 입력 시, state도 그때그때 바뀌면(onChange) 된다.
  • 그리고 이 변경된 state와 input의 value 또한 같게 작성해야 한다.
  input과 textarea value의 변경에 따라, react의 state가 변경된다.
import "./styles.css";                        
import React, { useState } from "react";   

export default function App() {
  const [username, setUsername] = useState(""); // 🟡 state 변수명은 username
  const [msg, setMsg] = useState("");         // 🟣 state 변수명은 msg

  return (
    <div className="App">
    
      <div>{username}</div>   
      <input
        type="text"
        value={username}                    // 🟡 input의 value도 state 값으로 변경된다.
        onChange={(event) => setUsername(event.target.value)}
        placeholder="여기는 인풋입니다."
        className="tweetForm__input--username">
      
  // 🟡 입력이 감지되면 setUsername함수에 해당 인풋박스 속 내용이 인자로 전달되고 state 값도 변경된다.    
      </input>
      
      <div>{msg}</div>
      <textarea
        placeholder="여기는 텍스트 영역입니다."
        className="tweetForm__input--message" value={msg}
        onChange={(event) => setMsg(event.target.value)} >
        
  // 🟣 입력이 감지되면 setMsg함수에 해당 인풋박스 속 내용이 인자로 전달되고 state 값도 변경된다.
  // 🟣 textarea의 value 속성도 state 값이 들어간다.
       
      </textarea>
      
    </div>
  );
}
 

728x90
⬆︎