9. 이벤트 핸들링(Event Handling)

CDH·2025년 4월 29일

이벤트(Events).

  • 브라우저에서 사용자의 조작이나 환경의 변화로 벌어진 사건
    • 예시) 사용자가 버튼을 클릭한 사건 – 버튼 클릭 이벤트

  • 이벤트 핸들링
    • 다양한 이벤트를 원하는 대로 처리하는 것

  • 이벤트 핸들러(Event Handler)
    • 어떤 이벤트가 발생했을 때 해당 이벤트를 처리하는 함수
    • 이벤트 리스터(Listener) 라고도 한다

이벤트의 선언은 카멜 표기법(Camel Case) 로 사용한다.

함수 컴포넌트의 이벤트 처리

  • 이벤트 핸들러를 함수로 정리하거나, 화살표 함수를 사용하여 정의한다
import React, { useState } from "react";

function ToggleFunc(props) {
  const [isToggleOn, setIsToggleOn] = useState(true);

  // 방법 1. 함수를 정의
  function handleClick() {
    setIsToggleOn((isToggleOn) => !isToggleOn);
  }

  // 방법 2. arrow function을 사용하여 정의
  const handleClick2 = () => {
    setIsToggleOn((isToggleOn) => !isToggleOn);
  };

  return (
    <button onClick={handleClick}>
      {isToggleOn ? "on" : "off"}
    </button>
  );
}

export default ToggleFunc;

Arguments 전달하기

  • 함수 컴포넌트에서의 Arguments(매개변수)
  • 함수에 전달할 데이터 (event handler에 전달할 데이터)
  • 매개변수가 없을 때는 그냥 함수 이름만 적으며, event 인자 값이 자동으로 전달 됨.
    ㆍ 매개변수를 전달할 때는 함수형으로 작성해야 함.( ( ) => 함수(인자) )
import React, {useState, useMemo} from "react";

function Sample2(props) {

    function handelClick1() { // 인자값 x
        console.log("handleClick1()");
    }

    function handleClick2(message) {
        console.log(message);
    }

    return (
        <div>
            <button onClick={ handelClick1 }>클릭1</button>

            <button onClick={ handleClick2('hello-bad') }>클릭2</button> {/*이 코드는 정상적으로 작동하지 않음 */}
            <button onClick={ () => handleClick2('hello-good') }>클릭3</button>
        </div>
    )
}

export default Sample2;

Click1 은 매개변수 없이 호출하는 방식
Click2 는 매개변수 전달의 잘못된 예 (버튼 누르기 전에 실행됨!)
Click3 은 매개변수 전달의 올바른 예 (함수형으로 감싸기)


아래는 화면에 출력되는 버튼


함수 컴포넌트의 이벤트 처리

  • 버튼 및 input 박스 이벤트 처리 예시

import React, { useState } from "react";

export default function Sample2() {
    const [isChecked, setIsChecked] = useState(false);

    function handleClick(event) {
        console.log(event);
        event.target.innerText += "*";
    }

    const handleCheckboxChange = (event) => {
        console.log(event.target.checked);
        setIsChecked(!isChecked);
    };

    return(
        <div>
            <button onClick={handleClick}> 클릭 </button>
            <input type='checkbox'
                checked={isChecked}
                onChange={handleCheckboxChange}
            />
        </div>
    );
} 


약관 동의 페이지 실습하기

import React, { useState } from 'react';

export default function Join() {
    // [필수] 약관 체크 상태
    const [isTermsAgreed, setIsTermsAgreed] = useState(false);

    // [선택] 광고 마케팅 체크 상태
    const [isMarketingAgreed, setIsMarketingAgreed] = useState(false);

    // [필수] 약관 동의 체크시 이벤트 핸들러
    const handleTermsChange = (event) => {
        setIsTermsAgreed(event.target.checked);
    };

    const handleMarketingChange = (event) => {
        setIsMarketingAgreed(event.target.checked);
    };

    const handleSignup = () => {
        alert('가입이 완료되었습니다!');
    };

    return (
        <div>
            <h2>약관 동의</h2>
            <label>
                <input
                    type="checkbox"
                    checked={isTermsAgreed}
                    onChange={handleTermsChange}
                />
            [필수] 약관에 동의합니다.
            </label>
            <br />
            
            <label>
                <input
                type='checkbox'
                checked={isMarketingAgreed}
                onChange={handleMarketingChange}
                />
            </label>
            [선택] 광고 마케팅에 동의합니다
            <br />

            <button
                onClick={handleSignup}
                disabled={!isTermsAgreed}>
            가입
            </button>
        </div>
    );

}

'가입' 버튼을 눌렀을 때 [필수] 항목이 체크된 경우에만 실행된다


조건부 렌더링

  • 어떠한 조건에 따라서 렌더링이 달라지는 것
  • 조건문의 결과 true/false에 따라서 렌더링을 다르게 하는 것
    • ex. 조건문의 값이 true이면 버튼을 보여주고 false이면 버튼을 가림
import React from 'react';
function UserGreeting(props) { return <h1>다시 오셨군요!</h1> }
function GuestGreeting(props) { return <h1>로그인을 해주세요.</h1> }
export default function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
	if(isLoggedIn) {
		return <UserGreeting />;
	}
	return <GuestGreeting />;
}

if 부터 return문까지 조건부 렌더링

  • Greeting 컴포넌트 – props로 들어오는 isLoggedIn의 값에 따라서 화면에 출력되는 결과가 달라짐 (조건부 렌더링)

자바스크립트의 Truthy와 Falsy


엘리먼트 변수

  • 조건부 렌더링을 할 때, 렌더링해야 될 컴포넌트를 변수처럼 다루고 싶을 때 사용
    • 컴포넌트(리액트 엘리먼트)를 변수처럼 저장해서 사용하는 방법

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

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      로그인
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      로그아웃
    </button>
  );
}

export default function LoginControl() {
  const [isLogged, setIsLogged] = useState(false);

  const handleLogin = () => {
    setIsLogged(true);
  };

  const handleLogout = () => {
    setIsLogged(false);
  };

  let button;

  if (isLogged) {
    button = <LogoutButton onClick={handleLogout} />;
  } else {
    button = <LoginButton onClick={handleLogin} />;
  }

  return (
    <div>
      <Greeting isLoggedIn={isLogged} />
      {button}
    </div>
  );
}

인라인 조건

  • 인라인 조건(Inline Conditions)
    • 조건문을 코드 안에 직접 작성하는 것
    • 인라인 If, 인라인 If-Else

  • 인라인 If
    - 조건에 따라 엘리먼트를 보여주거나 감출 때 사용

    - If문과 동일한 효과를 내는 논리곱 연산자(& ) 사용 (AND 연산자)

    - 양쪽에 나오는 조건문이 모두 참인 경우에만 전체 결과가 true
    - 첫 번째 조건문이 true이면 두 번째 조건문 평가
    - 첫 번째 조건문이 false이면 전체 결과가 false이므로 두 번째 조건문은 평가하지 않음(단축 평가)


- 인라인 If

• 조건문이 true이면 오른쪽에 나오는 엘리먼트가 결과값, false이면 false가 결과값
• && 연산자를 JSX 코드 안에 중괄호를 사용하여 직접 넣는 방법

주의 사항: 조건문에 Falsy expression을 사용하면 뒤에 나오는 expression은
평가되지 않지만 Falsy expression의 결과값(0)이 리턴되므로 주의


인라인 If-Else

• 조건문에 따라 다른 엘리먼트를 보여줄 때 사용
• 삼항 연산자를 사용

조건문 ? 참일 경우 : 거짓일 경우


• 인라인 If-Else

• 예시) 이전 엘리먼트 변수 예제를 삼항 연산자로 변경

컴포넌트 렌더링 막기

  • 컴포넌트를 렌더링하지 않는 방법
    • 컴포넌트를 렌더링 하고 싶지 않을 때 null을 리턴
    • 리액트에서는 null을 리턴하면 렌더링하지 않음

    ※ 클래스 컴포넌트의 render() 함수에서 null을 리턴하는 것은 컴포넌트의 생명주기 함수에 영향을 미치지 않음. (생명주기 함수가 정상적으로 호출됨)


실습. 로그인 상태 툴바 (Toolbar)

• 로그인 버튼과 상태를 표시하는 툴바 구현

Toolbar.jsx

import React from "react";

export default function Toolbar(props) {
    const { isLogin, onClickLogin, onClickLogout } = props;

    return (
        <div style={{ margin: 10 }}>
            { isLogin ?
            <button onClick={onClickLogout}>로그아웃</button> : <button onClick={onClickLogin}>로그인</button>
            }
            {isLogin && <span> 환영합니다! </span>}
        </div>
    );
}

LandingPage.jsx

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

export default function LandingPage(props) {
    const [isLogin, setIsLogin] = useState(false);

    const onClickLogin = () => {
        setIsLogin(true);
    }
    const onClickLogout = () => {
        setIsLogin(false);
    }

    return (
        <div>
            <Toolbar
                isLogin={isLogin}
                onClickLogin={onClickLogin}
                onClickLogout={onClickLogout}
            />
            <hr />

            <div style={{ padding: 10 }}>
            Contents page
            </div>
        </div>
    );
}

실습. Todolist (조건부 및 이벤트 적용)

참고사항

  • 참고 – 할 일 정보 객체
    • 기존에는 할 일 정보를 단순 “텍스트”만 배열에 저장하였지만,
    id, 텍스트(할 일), 체크 여부를 하나의 객체로 저장하여 처리할 필요성이 있음


  • 참고 - 남은 할 일 개수 관리 방법
    • 할 일 목록(todos)의 개수를 표시하는 방법(선택적으로 적용함)


  • 참고 - 취소선 CSS
    • textDecoration 속성
    • “none” : 기본 속성
    • “line-throught”: 취소선

최종 코드 (Todolist)

Join.jsx

import React, { useState } from 'react';
import Toolbar from './Toolbar';
import TodoList from './Todolist';

export default function Join() {
    const [isLogin, setIsLogin] = useState(false);

    const onClickLogin = () => setIsLogin(true);
    const onClickLogout = () => setIsLogin(false);

    /*
    // [필수] 약관 체크 상태
    const [isTermsAgreed, setIsTermsAgreed] = useState(false);
    // [선택] 광고 마케팅 체크 상태
    const [isMarketingAgreed, setIsMarketingAgreed] = useState(false);
    // [필수] 약관 동의 체크시 이벤트 핸들러
    const handleTermsChange = (event) => {
        setIsTermsAgreed(event.target.checked);
    };
    const handleMarketingChange = (event) => {
        setIsMarketingAgreed(event.target.checked);
    };
    const handleSignup = () => {
        alert('가입이 완료되었습니다!');
    };
*/
    return (
        <div style={{ margin: 1 }}>
            <Toolbar
                isLogin={isLogin}
                onClickLogin={onClickLogin}
                onClickLogout={onClickLogout}
            />
            <hr />

            {!isLogin ? (
                <div style={{ padding: 10 }}>
                    서비스를 이용하려면 로그인하세요!
                </div >
            ) : (
                <div style={{ display: 'flex', justifyContent: 'flex-start', padding: 10 }}>
                    <TodoList />
                </div>

            )}
        </div>
    );


}

TodoList.jsx

import React, { useState, useEffect } from "react";

export default function TodoList() {
    const [todos, setTodos] = useState([]);
    const [input, setInput] = useState("");
    const [todoNums, setTodoNums] = useState(0);

    const addTodo = () => {
        if (input === "") return;
        const todo_item = {
            id: new Date(),
            text: input,
            checked: false,
        }
        setTodos([...todos, todo_item]);
        setInput("");
    };

    const deleteTodo = (id) => {
        setTodos(todos.filter(todo => todo.id !== id));
    };

    const toggleComplete = (id) => {
        setTodos(todos.map((todo) =>
            todo.id === id ? { ...todo, checked: !todo.checked } : todo
        )
        );
    }

    useEffect(() => {
        const count = todos.filter(todo => !todo.checked).length;
        setTodoNums(count);
    }, [todos]);

    return (
        <div style={{ margin: 10 }}>
            <h2>할 일 목록</h2>
            <div>
                <input type="text" value={input}
                    onChange={(e) => setInput(e.target.value)} />
                <button onClick={addTodo}>추가</button>
            </div>
            <p>남은 할 일 : {todoNums}</p>
            {
                todos.map((todo, index) => (
                    <div key={index} className="todo-item">
                        <input type="checkbox"  checked={todo.checked}
                            onChange={() => toggleComplete(todo.id)} />
                        <span style={{ textDecoration: todo.checked ? 'line-through' : 'none' }}>
                            {todo.text}
                        </span>
                        <button
                            onClick={(e) => deleteTodo(todo.id)}
                            style={{ margin: 5 }}
                        >삭제</button>
                    </div>
                ))
            }
        </div>
    );
}

0개의 댓글