091
[JS/React] 이벤트 핸들링 & 조건부 렌더링 본문
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>


- 인라인 조건이란 조건문을 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>}
{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은 객체로 내부 값을 수정할 때는 전체 복사, 해당 값 수정(같은 키값으로 주어진 속성값은 뒤에 온 걸로 덮어쓰여집니다.)으로 바꿔주면 됩니다.
->
'Programming Language > JavaScript&TypeScript' 카테고리의 다른 글
| [JS/React] 리액트 라우터(React Router) (0) | 2026.05.24 |
|---|---|
| [JS/React] 리스트와 키, 입력 폼 (0) | 2026.04.18 |
| [JS/React] JSX(4): Lifecycle, Hook (0) | 2026.04.12 |
| [JS/React] JSX(3): Component 실습, State (0) | 2026.04.12 |
| [JS/React] JSX(2): Component, Element (1) | 2026.04.10 |
