[Seeds-study] (React/리액트) 4주차 이벤트 처리와 폼

2024. 3. 18. 03:16·Front-end/React 리액트
[STUDY 진행 과정은 깃허브에 계속 commit 하고 있습니다] (많관부!)
https://github.com/just-stopyoon/Seeds-study

4주차 과제 내용

버튼 클릭시 body의 색이 변경되도록 이벤트 핸들러를 작성하고 연결하기

이벤트 핸들러 (event handling)이란?

이벤트 핸들링은 웹 페이지에서 사용자 상호작용과 관련된 동적인 기능을 구현하기 위해 사용되는 기술이다.

그렇다면 이벤트는?

웹 페이지에서 '이벤트'는 사용자가 발생시키는 마우스 클릭, 키보드 입력, 스크롤 등과 같은 행위를 의미한다.

 

따라서,

이벤트 핸들링은 이러한 이벤트가 발생했을 때, 브라우저가 이를 감지하고, 이벤트를 처리할 수 있는 JavaScript 코드를 실행하는 과정을 말한다. 결국, 이벤트 핸들러 (handler)는 이벤트가 발생했을 때 실행될 함수를 말한다.

 

이벤트 제어하기

React 요소의 이벤트를 제어하는 건 DOM 요소 이벤트를 제어하는 것과 매우 유사합니다.
하지만, 몇 가지 문법적인 차이가 있습니다.

 

 

- React 이벤트는 소문자 대신 camelCase를 사용한다.

- JSX에 문자열 대신 함수를 전달한다.

 

예를 들어서 HTML에서 이벤트를 넣을 때, 이렇게 쓰이지만,

<button onClick="activateLasers()">
	Activate Lasers
</button>

 

React에서는 이렇게 쓰인다.

<button onClick={activateLasers}>
  Activate Lasers
</button>

 

함수가 "" 안에서 선언되지 않고 {} 를 통해 선언되는 것을 볼 수 있다.

 

다른 차이점으로는 React에서 기본 동작을 막기 위해 false 리턴을 사용할 수 없다는 것이다.

반드시 명시적으로 preventDefault 를 호출해야 한다.

 

예를 들어서 HTML에서 새로운 페이지를 여는 기본 동작을 막으려면 이렇게 작성해야 한다.

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>

 

하지만, React에서는 더 복잡하다.

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

 

여기서 e는 합성 이벤트이다.

React는 W3C spec에 따라 이런 합성 이벤트를 정의하므로 브라우저 간 호환성을 걱정할 필요는 없다.

 

React는 일반적으로 DOM 요소가 생성된 후에 리스너를 추가하기 위해 addEventListener를 호출할 필요가 없다.

대신 요소가 대신 렌더링 될 때 리스너를 제공한다.

 

ES6 class를 이용해 요소를 정의할 때 이벤트 핸들러의 일반적인 패턴은 클래스의 메서드 형태이다.

에를 들어, 아래 Toggle 컴포넌트는 "ON"과 "OFF" state를 유저가 토글할 수 있게 하는 버튼을 렌더링한다.

class Toggle extends React.Component {
	constructor(props) {
    	super(props);
        this.state = {isToggleOn: true};
        this.handleClick = this.handleClick.bind(this);
	}

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

 

주의 할 점!

JSX 콜백에서 this의 의미에 대해 주의해야 한다.

JavaScript에서 클래스 메서드는 기본적으로 bound 되지 않는다.

만약 this.handleClick 바인드를 잊은 채로 onClick에 전달하면, this는 함수가 실제로 호출될 때 undefined로 취급된다.

 

이건 React에서 정의한 동작이 아니다. JavaScript의 함수의 동작 방식의 일부이다.

일반적으로 onClick={this.handleClick} 처럼 () 없이 메서드를 참조하면, 그 메서드를 bind 해야 한다.

 

이벤트 핸들러에 인수 전달하기

반복 안에서 보통 이벤트 핸들러에 추가 파라미터를 전달하고 싶은 경우!

에를 들어, 만약 id가 원시적인 ID 라면, 아래처럼 전달할 수 있다.

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

 

위 두 라인은 동일하며, arrow functions과 Function.prototype.bind를 각각 사용하고 있다.

 

두 경우 모두, React 이벤트를 나타내는 e 인수는 ID 뒤에 두 번째 인수로 전달된다.

arrow function을 사용하여 명시적으로 전달해야 하지만, bind를 사용하면 추가 인수가 자동으로 전달된다.

 

폼

HTML 폼 (form) 요소는 폼 요소 자체가 내부 상태를 가지기 때문에
React에서 다른 DOM 요소가 조금 다르게 동작합니다.

 

React에서는 그냥 동작하지만, 대부분의 경우 form 제출을 처리하고 사용자가 form에 입력한 데이터에 접근할 수 있게 하는 JavaScript 함수를 가지는 게 편하다.

이 동작을 위한 표준 방식은 "제어되는 컴포넌트 (Controlled Components)" 기법을 사용하는 것이다.

 

제어되는 컴포넌트 (Controlled Components)

HTML에서 <input>, <textarea>, <select> 같은 form 요소는 자기만의 state를 가지고 유저 입력에 따라 업데이트 된다.

React에서, 변경 가능한 state는 일반적으로 컴포넌트의 state 속성에 존재하며, setState() 로만 업데이트 할 수 있다.

 


다시 한 번 복습

이벤트 핸들러

컴포넌트 내부에 이벤트 핸들러를 정의해서 사용한다.

- hanlde + 동사 + 명사 형태로 이벤트 핸들러의 기능을 알 수 있도록 네이밍한다. (CamelCase)

- 이벤트 속성에 전달만 해야한다.

 

일반 js처럼 handleShowAlert()라고 적으면 바로 실행된다

import './App.css';

export default function Button() {
  function handleShowAlert() {
    alert('클릭!');
  }

  return (
    <button className="btn" type="button"
    	onClick={handleShowAlert}>
      	클릭
    </button>
  );
}

 

그런데 만약 실행할 함수가 짧다면?

만약 실행할 함수가 따로 정의하지 않아도 될 정도로 짧다면?

익명 함수를 사용해서 onClick 이벤트에서 함수 작성을 해서 사용 가능하다.

이것을 인라인 이벤트 핸들러 라고 한다.

import './App.css';

export default function Button() {
  return (
    <button className="btn" type="button"
    	onClick={() => alert('클릭!')}>
        클릭
    </button>
  );
}

 

이벤트 핸들러에서 prop 읽기

AlertButton 컴포넌트 만들어서 여기에 props를 전달해보자!

import './App.css';

// AlertButton 컴포넌트 만들기
function AlertButton({ message, children }) {
  return (
    <button type="button" className="btn" onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function App() {
  function handleShowAlert() {
    alert('클릭');
  }
  return (
    <div className="app">
      {/* 버튼 디자인은 같지만 기능은 상이합니다 */}
      <AlertButton message="영화보는중입니다">영화보는중</AlertButton>
      <AlertButton message="드라마보는중입니다">드라마보는중</AlertButton>
    </div>
  );
}

 

AlertButton 컴포넌트를 만들고 prop 전달을 해서

각각 버튼을 누를 때마다 children prop이 전달되게 했다.

부모의 이벤트 핸들러를 prop으로 전달

만약 공통 버튼 컴포넌트를 사용하는 부모 컴포넌트의 기능이 서로 다른 경우 어떻게 해야할까?

import './App.css';
// 공통 버튼 컴포넌트 만들기
function Button({ children, onClick }) {
  return (
    <button className="btn" onClick={onClick}>
      {children}
    </button>
  );
}
// 플레이 버튼 기능 컴포넌트
function PlayButton({ movieName }) {
	// 내장함수를 사용
  function handlePlayMovie() {
    alert(`${movieName} 재생중 !`);
  }
  return <Button onClick={handlePlayMovie}>{movieName} play</Button>;
}
// 업로드 버튼 기능 컴포넌트
function UploadButton() {
	// 인라인 이벤트 핸들러로 작성
  return <Button onClick={() => alert('업로드중!')}>Upload image</Button>;
}

export default function App() {
  return (
    <div className="app">
      <PlayButton movieName="파묘"></PlayButton>
      <UploadButton></UploadButton>
    </div>
  );
}

 

재생과 업로드 두 가지 기능의 버튼이 있어야 하고, 둘이 같은 디자인을 공유한다면

공통 디자인 버튼 컴포넌트를 만들고, prop들을 구조분해로 받게 한 뒤

플레이 기능, 업로드 기능은 기능이 다르므로 두 개의 기능 컴포넌트로 분리한다.

 

플레이 기능에는 내장함수를 추가하고

업로드 기능에는 익명 함수를 사용해서 이벤트 핸들러를 작성한 코드이다.

 

하나의 컴포넌트에 여러 상호작용이 있을 경우

이런 경우, 이벤트 핸들러에 prop의 이름을 서로 다르게 전달하면 된다.

이벤트 핸들러 prop 이름은 on + 동사 + 명사 형태로 작성하는 게 좋다.

import './App.css';

function Button({ onClick, children }) {
  return (
    <button className="btn" onClick={onClick}>
      {children}
    </button>
  );
}

function ToolBar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>PlayMovie!</Button>
      <Button onClick={onUploadImage}>업로드중</Button>
    </div>
  );
}
export default function App() {
  return (
    <div className="app">
      <ToolBar
        onPlayMovie={() => alert('영화 재생 중')}
        onUploadImage={() => alert('업로드 중')}
      ></ToolBar>
    </div>
  );
}

 

근데 보통은 이렇게 잘 안하고, 전에 했던 것처럼 기능별로 컴포넌트를 분리하는 게 일반적이라고 한다 !!

 

이벤트 전파 막기

자식 컴포넌트와 부모 컴포넌트에 동일 이벤트가 걸린 경우, 이벤트 전파가 일어난다.

import './App.css';

function Button({ onClick, children }) {
  return (
    <button
      className="btn"
      onClick={(e) => {
        e.stopPropagation();
        onClick();
      }}
    >
      {children}
    </button>
  );
}
export default function Toolbar() {
  return (
    <div
      className="Toolbar"
      onClick={() => {
        alert('You clicked on the toolbar!');
      }}
    >
      <Button onClick={() => alert('Playing!')}>Play Movie</Button>
      <Button onClick={() => alert('Uploading!')}>Upload Image</Button>
    </div>
  );
}

 

부모와 자식 컴포넌트에 동일하게 alert() 이벤트가 걸린 경우, 버튼 컴포넌트에서 e.stopProgation()을 사용하면

해당 버튼을 클릭했을 때 이벤트의 전파가 중지되고 툴바를 클릭했을 때의 이벤트가 발생하지 않는다.

 

버튼 컴포넌트의 클릭 이벤트는 해당 버튼이 클릭되었을 때만 실행되고 툴바 클릭은 다른 곳에서 처리된다.

 

각 버튼의 onClick prop에 전달된 함수는 클릭 시 각각 "Playing!" or "Uploading!" 알림을 띄운다.

 

form의 기본이벤트 막기

form 안의 submit 버튼을 클릭하면 submit 이벤트가 발생하면서 페이지가 새로고침 되므로,

"기본 이벤트를 막아야 한다."

import './App.css';

export default function App() {
  return (
    <div className="app">
      <form
        onSubmit={(e) => {
          e.preventDefault();
          alert('내용을 입력해주세요');
        }}
      >
        <input type="text" />
        <button type="submit">전송</button>
      </form>
    </div>
  );
}

4주차 과제

버튼 클릭 시 body 색이 변경되도록 이벤트 핸들러를 작성하고 연결하기 (폰트 컬러까진 X)

더보기
export default function App() {

  return (
    <div className="app">
      <button className="btn">
        색상변경
      </button>
    </div>
  );
}

원래는 다크모드 ↔ 라이트모드 변경을 만들려고 했는데 폰트 컬러까지는 건들지 말라고 해서

일단 배경색만 바뀌는 걸로 해보려고 한다!

function Button({ onClick, isBodyColor }) {
  return (
    <div className="set-middle">
      <button className="btn" onClick={onClick}>
        {isBodyColor ? "원래대로" : "배경색 바꾸기"}
      </button>
    </div>
  );
}

 

후다닥 버튼 컴포넌트 만들어주고,,,

function App() {
  const [isBodyColor, setIsBodyColor] = useState(false);

  const toggleBodyColor = () => {
    setIsBodyColor(!isBodyColor);
  };

  return (
    <div className={`App-back ${isBodyColor ? "change-color" : ""}`}>
      <Header />
      <Profile />
      <Section />
      <Button onClick={toggleBodyColor} isBodyColor={isBodyColor} />
      <Footer />
    </div>
  );
}

 

App 컴포넌트에 useState를 이용해서 함수 추가해줬다! (토글 기능으로 왔다리 갔다리 할 수 있게 만들어줬다!)

결과물)

요랬는데 ~
요래 됐슴당~!!~!

 

4주차 과제 끝!

하지만 배경 바뀐 버전에서 button 과 footer 사이에 진짜 킹받는 저 공백은 뭘까,,, 나중에 해결해보도록 하자...

개강하면서 너무 정신없어서 부랴부랴 벼락치기로 한 과제 ㅠㅅㅠ

앞으로는 정신 차리고 미리미리 합시다!!

저작자표시 (새창열림)

'Front-end > React 리액트' 카테고리의 다른 글

[Seeds-study] (React/리액트) 5주차 React Hooks (useState)  (1) 2024.03.24
[Seeds-study] (React/리액트) 3주차 컴포넌트에 prop전달 / 조건부 렌더링  (0) 2024.03.10
[Seeds-study] (React/리액트) 2주차 JSX와 컴포넌트 기초  (0) 2024.03.03
[Seeds-study] (React/리액트) 1주차 React 개발 환경 설정  (2) 2024.02.25
[React 리액트/CSS] CSS가 제대로 적용되지 않을 때 해결법  (2) 2024.02.20
'Front-end/React 리액트' 카테고리의 다른 글
  • [Seeds-study] (React/리액트) 5주차 React Hooks (useState)
  • [Seeds-study] (React/리액트) 3주차 컴포넌트에 prop전달 / 조건부 렌더링
  • [Seeds-study] (React/리액트) 2주차 JSX와 컴포넌트 기초
  • [Seeds-study] (React/리액트) 1주차 React 개발 환경 설정
vV최강양파Vv
vV최강양파Vv
양파 갓생 살기 프로젝트
  • vV최강양파Vv
    just-stop
    vV최강양파Vv
  • 전체
    오늘
    어제
    • just-stopyoon (22)
      • Front-end (1)
        • mosAIc 리팩토링 (0)
        • React 리액트 (6)
        • SNAPINFO 리팩토링 (0)
      • BoostCamp (4)
      • Problem Solving (7)
        • 백준 (Baekjoon) (0)
        • 프로그래머스 (Programmers) (7)
      • ECONOMY (4)
        • Daily 금융 상식 (4)
  • 블로그 메뉴

    • 홈
    • 방명록
  • 링크

    • 멍청고양이
    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    가장 큰수
    독서
    해시
    코딩테스트
    프론트엔드
    PS
    the money book
    개발
    큐
    CSS
    프로그래머스
    깃허브
    구름톤
    코드
    금융생활
    gihub
    구름톤챌린지
    코딩 문제
    react
    정렬
    책 후기
    금융상식
    front-end
    리액트
    코딩
    독후감
    react개발
    토스
    파이썬
    알고리즘
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
vV최강양파Vv
[Seeds-study] (React/리액트) 4주차 이벤트 처리와 폼
상단으로

티스토리툴바