091

[JS/React] 이벤트 핸들링 & 조건부 렌더링 본문

Programming Language/JavaScript&TypeScript

[JS/React] 이벤트 핸들링 & 조건부 렌더링

공구일 2026. 4. 12. 22:10
728x90

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

 

- 이벤트(Event)란 브라우저에서 사용자의 조작이나 환경 변화로 발생하는 사건을 의미합니다. 

function handleClick() { //일반함수형식
  setIsToggleOn((isToggleOn) => !isToggleOn);
}

const handleClick2 = () => { //화살표함수형식
  setIsToggleOn((isToggleOn) => !isToggleOn);
}

<button onClick={handleClick}>
  {isToggleOn ? 'On' : 'Off'}
</button>

->리액트에서는 함수를 그대로 중괄호에 감싸서 사용합니다. 이전 설명 글에서 언급했듯이 인자가 없는 경우에는 함수만 넘겨도 되지만 인자가 있는 경우에는 {()=>함수명(인자)}로 넘겨줘야합니다. 그렇지 않으면 뒤에 함수명(인자)에서 괄호를 보고 지금 당장 실행하라는 의미로 해석됩니다.

const showAlert = () => alert("안녕!"); 
// 사용: <button onClick={showAlert}>인사하기</button>

const printLocation = (e) => console.log("클릭한 X좌표:", e.clientX);
// 사용: <button onClick={printLocation}>좌표 찍기</button>

const deleteItem = (id) => console.log(`${id}번 상품 삭제!`);
// 사용: <button onClick={() => deleteItem(3)}>삭제</button>

-> onClick처럼 리액트의 이벤트 시스템에 함수를 맡겨두면, 실행할 때 방금 일어난 이벤트를 첫번째 자리에 넣어주기 때문에 showAlert와 printLocation같은 예제는 모두 함수 그대로 넣어사용하면 됩니다.

 

+) 버튼 및 input 박스처리

import React, { useState } from "react";

export default function MyButton() {
    const [btnStr, setBtnStr] = useState("클릭");
    const [isChecked, setIsChecked] = useState(false);
    function handleClick(event) {
        console.log(event);
        setBtnStr(btnStr + "*");
    }
    const handleCheckboxChange = (event) => {
        console.log("실제값: "+event.target.checked);
        console.log("state: "+isChecked);
        setIsChecked(!isChecked);
    };
    return (
        <div>
            <button onClick={handleClick}>
                {btnStr}
            </button>
            <input type="checkbox"
                checked={isChecked}
                onChange={handleCheckboxChange}
            />
        </div>
    );
}

-> event를 출력해보면 아래와 같은 결과가 출력됩니다. 그리고 handleCheckboxChange 내부에서 event.target.checked와 isChecked를 출력하는 결과값은 반대입니다.(렌더링 되기 전 내용인 state값과 지금 checkbox 바로 그 태그 자체 값은 차이가 납니다.)

+) 약관 동의 페이지

import {useState} from "react";

export default function Join(){
    const [isRequired, setIsRequired] = useState(false);
    const [isSelected, setIsSelected] = useState(false);

    const handlerCheckboxRequired = (e) => {
        //setIsRequired(!isRequired);
        setIsRequired(e.target.checked);//브라우저가 던져주는 이벤트 객체의 최신 값으로 설정하는 것을 권장
    }
     const handlerCheckboxSelected = (e) => {
        //setIsSelected(!isSelected);
        setIsSelected(e.target.checked); 
    }

    return(
        <div>
            <h2>약관 동의</h2>
            <input type="checkbox"
                checked={isRequired}
                onChange={handlerCheckboxRequired}/>[필수] 약관에 동의합니다.<br />
            <input type="checkbox"
                checked={isSelected}
                onChange={handlerCheckboxSelected}/>[선택] 광고•마케팅에 동의합니다.<br />
            <button onClick={()=>alert("가입이 완료되었습니다.")} disabled={!isRequired}>가입</button>
        </div>
    )
}

-> 만약에 checked나 value 속성을 리액트 state와 연결했다면 onChange를 꼭 설정해줘야합니다. 그렇지 않으면 readonly 상태가 되기 때문에 고정하는 목적이 아니라면 꼭 onChange를 작성해줘야합니다.

 

2. 조건부 렌더링(Conditional Rendering)

- 조건부 렌더링이란 말 그대로 조건의 결과에 따라 화면에 렌더링되는 내용을 다르게 하는 것입니다.

function UserGreeting(props) { return <h1>다시 오셨군요!</h1> }
function GuestGreeting(props) { return <h1>로그인을 해주세요.</h1> }

export default function Greeting({isLoggedIn}) {
    if (isLoggedIn) {
        return <UserGreeting />;
    }
    return <GuestGreeting />;
}

-> isLoggedIn의 t/f 결과에 따라 실행되는 렌더링 값이 다르게 지정해둔 부분이 바로 조건부 렌더링입니다.

 

- 자바스크립트에는 true로 여겨지는 Truthy값과 false로 여겨지는 falsy값이 존재하며 각각의 값은 아래와 같습니다. 이부분을 조건부 렌더링 학습을 하며 알아야하는 부분인 이유는 {조건 && 컴포넌트나 태그} 상황에서 조건에 0과 같은 값이 들어가면 0이 그대로 화면에 출력하는 함정이 잇습니다.

 <h1>{count &&`총 ${count}번 클릭했습니다.`}</h1>

0이 출력됨/truthy값과 falsy값

- 인라인 조건이란 조건문을 JSX 코드 안에서 직접 작성하는 방법으로 인라인 if와 인라인 if-else로 나뉩니다.

true  && expression  →  expression (표현식 출력)
false && expression  →  false (아무것도 출력 안 함)

• 인라인 if(&& 연산자)는 조건에 따라 엘리먼트를 보여주거나 감출 때 주로 사용합니다. 위와 같은 0 오류가 아닌 이상 false일 때는 아무것도 출력되지 않습니다.

//조건문 ? 참일 경우 : 거짓일 경우
<b>{isLoggedIn ? '로그인' : '로그인하지 않은'}</b> 상태입니다.

• 인라인 if-else(삼항 연산자)는 조건에 따라 서로 다른 두 엘리먼트 중 하나를 보여줄 때 사용합니다.

 

- 특정 조건에서 컴포넌트를 아예 화면에 표시하지 않으려면 null을 반환합니다. 리액트는 null을 반환하면, 해당 컴포넌트를 렌더링하지 않습니다.

function WarningBanner({ warn }) {
  // warn 값이 false면 즉시 null을 반환하고 렌더링을 종료해버립니다.
  if (!warn) {
    return null; 
  }

  // warn 값이 true일 때만 아래 화면(JSX)을 그립니다.
  return (
    <div className="warning">
      경고! 문제가 발생했습니다!
    </div>
  );
}

-> display:none과 비슷하게 느껴질 수 있지만, return null을 통해 태그 자체를 생성하지 않아 메모리를 절약하여 성능을 좋게 만들 수 있습니다.

 

+) 로그인 상태 툴바 실습

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

export default function LandingPage(){
    const [isLoggedIn, setIsLoggedIn] = useState(false);

    const onClickLogin = () => {
        setIsLoggedIn(true);
    }
    const onClickLogout = () => {
        setIsLoggedIn(false);
    }
    return(
        <div>
            <Toolbar isLoggedIn={isLoggedIn} 
                    onClickLogin={onClickLogin}
                    onClickLogout={onClickLogout}/>
            <hr />
            <div>
                Contents page
            </div>
        </div>
    )
}
export default function Toolbar(props){
    const { isLoggedIn, onClickLogin, onClickLogout } = props;

    return(
        <div>
            {isLoggedIn ?
            <button onClick={onClickLogout}>로그아웃</button>
            :
            <button onClick={onClickLogin}>로그인</button>}
            &nbsp;
            {isLoggedIn && "환영합니다!"}
        </div>
    )
}

 

+) todo-list 디벨롭 실습

return(
    <div>
        <Toolbar isLoggedIn={isLoggedIn} 
                onClickLogin={onClickLogin}
                onClickLogout={onClickLogout}/>
        <hr />
        {isLoggedIn?
        <div>
            <TodoList />
        </div>
        :<p>서비스를 이용하려면 로그인하세요!</p>}
    </div>
)

-> 랜딩페이지를 위처럼 수정해줍니다.

import { useState,useEffect } from "react";
import "./style.css";

function TodoList(){
    const [todos, setTodos] = useState([]);
    const [input, setInput] = useState("");
 
    const changeInput = (str) => {
        setInput(str);
        //console.log(`[${input}]`);
    };
    
    const addTodo = () => {
        if(input.trim()==="") return;
         
        const todo_item = {
            id: Date.now(),//id: new Date(), 너무 큰 객체라서 가볍게 밀리초를 id로 지정
            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
        )); //todo_item 역시 객체이기 때문에 수정할 때 스프레드 구문을 사용해 일부를 수정해야합니다.
    }

    const remainingTodos = todos.filter(todo=>!todo.checked).length;

    return(
        <div className="container">
            <h2>할 일 목록</h2>
            <div className="input-container">
                <input type="text" 
                    value={input}
                    onChange={(e)=>changeInput(e.target.value)}/>
                <button onClick={addTodo}>추가</button>
            </div>
            <p>남은 할 일: {remainingTodos}개</p>
            <ul className="todo-list" >
            {
                todos.map(todo => ( //id를 사용할 것이기 때문에 index는 사용하지 않음
                    <li key={todo.id} 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={() => deleteTodo(todo.id)}>삭제</button>
                    </li>
                ))
            }
            </ul>
        </div>
    )
}

export default TodoList;

-> 각 항목마다 체크여부에 관련된 속성을 가져야하기 때문에 기본 input값에서 각각의 속성을 가지는 todo_item으로 수정해줍니다.

-> deleteTodo(): 원래는 기본적으로 map을 통해 제공되던 index값을 썼지만, 내부에 id 값을 가지게 됐기 때문에 id가 같지 않은 경우만 남기는 식으로 삭제해줍니다.

-> toggleComplete(): 버튼을 눌렀을 때 결국 todo_item의 속성을 바꾸는 것이 크게 보면 todos 내부 값을 바꾸는 것이기 때문에 setTodos()를 무조건 사요해야합니다. todo_item은 객체로 내부 값을 수정할 때는 전체 복사, 해당 값 수정(같은 키값으로 주어진 속성값은 뒤에 온 걸로 덮어쓰여집니다.)으로 바꿔주면 됩니다.

-> 

728x90