버튼을 누르고, 글자를 입력하고, 폼을 제출하는 사용자 동작에 화면이 반응하게 만드는 게 이벤트 처리예요. 이번 장은 onClick·onChange 같은 이벤트 속성과 핸들러 함수를 다룹니다. 흔히 하는 실수까지 짚어봐요.
preventDefault()로 기본 동작을 막을 수 있어요.이벤트(event)는 사용자가 화면에서 하는 동작이에요. 버튼을 클릭하거나, 입력창에 글자를 입력하거나, 폼을 제출하는 것 모두 이벤트예요. 리액트에서는 이런 이벤트에 함수를 연결해 반응하게 만들어요.
onclick처럼 전부 소문자였지만, JSX에서는 onClick처럼 카멜케이스(중간 글자를 대문자)로 써요. onChange, onSubmit, onMouseOver 모두 같은 규칙이에요.
즉 "on" 뒤 첫 글자가 대문자라는 것만 기억하면 돼요. onclick(❌) → onClick(✅).
// HTML (예전 방식)
<button onclick="handleClick()">클릭</button>
// JSX (리액트 방식) — 카멜케이스 + 중괄호
<button onClick={handleClick}>클릭</button>
| 이벤트 | JSX 속성 | 언제 |
|---|---|---|
| 클릭 | onClick | 버튼·요소를 눌렀을 때 |
| 입력 변경 | onChange | 입력창 값이 바뀔 때 |
| 폼 제출 | onSubmit | 폼을 제출할 때 |
| 마우스 올림 | onMouseOver | 요소 위에 마우스를 올렸을 때 |
| 원래(띄어 쓴 말) | camelCase |
|---|---|
| on click | onClick |
| on change | onChange |
| background color | backgroundColor |
| first name | firstName |
<button onclick="hello()">. 하지만 JSX는 HTML이 아니라 자바스크립트를 확장한 문법이라, 자바스크립트의 이름 짓기 규칙(카멜케이스)을 따라요.
// ✅ 올바름 — 카멜케이스
<button onClick={hello}>클릭</button>
<input onChange={handleChange} />
// ❌ 안 됨 — 소문자(HTML 습관)
<button onclick={hello}>클릭</button>
<input onchange={handleChange} />
③ 표기법 4종 비교
| 표기법 | 예시 | 주로 어디에 |
|---|---|---|
| camelCase 카멜 | onClick, backgroundColor | JS 변수·함수·이벤트 속성 |
| PascalCase 파스칼 | MyButton, UserName | 리액트 컴포넌트 이름 |
| snake_case 스네이크 | user_name | 파이썬 등 |
| kebab-case 케밥 | background-color | HTML·CSS |
MyButton, UserProfile, HttpRequest. 리액트 컴포넌트 이름과 클래스·타입·인터페이스 이름에 써요. (CH03에서 "컴포넌트는 대문자로 시작"이라고 한 게 바로 이거예요.)_로 잇고 전부 소문자예요. user_name, max_count. 뱀이 낮게 기어가는 모양이라 "스네이크"죠. 파이썬 변수·함수, 데이터베이스 컬럼명에 흔해요. (전부 대문자로 쓰면 MAX_SIZE처럼 상수용 "SCREAMING_SNAKE_CASE"예요.)-로 잇고 소문자예요. background-color, user-profile. 꼬치(케밥)에 꿰인 모양이죠. HTML 속성·CSS 속성·URL 경로·파일명에 써요. ⚠️ 자바스크립트 변수명엔 못 써요 — -를 뺄셈으로 읽어버리거든요.
MyButton), 변수·함수·이벤트 속성 = camelCase(handleClick, onClick, backgroundColor).
이벤트에 연결하는 함수를 이벤트 핸들러(handler)라고 불러요. 연결할 때는 함수의 이름(참조)만 넘겨요. 괄호 ()를 붙이면 지금 당장 호출해버리는 뜻이 되어 완전히 달라져요.
function App() {
function handleClick() {
alert('버튼이 눌렸어요!');
}
// JSX : ✅ 함수 자체(참조)를 전달 — 클릭할 때 실행됨
return <button onClick={handleClick}>클릭</button>;
}
onClick={handleClick()}처럼 괄호를 붙이면 클릭을 기다리지 않고 렌더링되는 순간 즉시 실행돼 버려요. 그 함수의 반환값이 onClick에 들어가므로, 대개 원하는 대로 동작하지 않아요.
비유: handleClick은 "요리사(함수)를 건네주는 것", handleClick()은 "요리사에게 지금 당장 요리시키고 그 결과 접시를 건네는 것"이에요.
// ❌ 잘못 — 렌더 시 즉시 호출되어 버림 (무한 실행/즉시 alert 등)
<button onClick={handleClick()}>클릭</button>
// ✅ 올바름 — 함수 참조만 전달, 클릭할 때 호출
<button onClick={handleClick}>클릭</button>
<button onClick={handleClick}>클릭</button>;
↑ ↑
이 중괄호 두 개
// 중괄호 { } 는 "여기 안에 자바스크립트 표현식을 넣는다" 라는 신호임. → JSX 문법 (JS 표현식을 넣으라는 신호)
// JSX 안에서 순수 자바스크립트 코드(변수, 함수, 계산식 등)를 쓰고 싶을 때 중괄호 { }로 감싸는 규칙이 있음
//✅ 위의 코드는 내부적으로 React.createElement 로 변환된다
React.createElement('button', { onClick: handleClick }, '클릭')
↑ ↑
여기서의 중괄호 두 개
// { onClick: handleClick }는 완전히 다른 괄호이다. → JS 문법 (JS 객체 리터럴 문법)
// 이 괄호는 자바스크립트의 객체 리터를을 만드는 중괄호이다. { } 안에 key: value 형태 → 예: { name: "철수", age: 20 }
handleClick vs handleClick()의 차이
handleClick→ "이 함수 자체"를 가리키는 것. 아직 실행 안 함. 나중에 필요할 때 실행해달라고 넘겨주는 것. 즉, "클릭하면 이걸 실행해줘" 라는 예약, 클릭할 때 실행됨 ✅ (우리가 원하는 동작)
handleClick()→ 괄호를 붙이는 순간, 자바스크립트는 "지금 당장 이 함수를 실행해!"라고 해석해요. 그리고 그 실행 결과값(반환값)을 가져와요. 예약이 아닌 코드를 읽는 그 순간 바로 실행해버림, 렌더링되는 순간 즉시 실행되고, 정작 클릭했을 때는 onClick에 undefined만 있어서 아무 반응 없음 ❌
onClick={handleClick()}을 쓰면 어떻게 되는가?
onClick에 뭘 넣어야 하지? handleClick()이라는 표현식을 계산해봐야겠다"
handleClick()을 실행함 → alert('버튼이 눌렸어요!')가 그 즉시 실행됨 (버튼 클릭 여부와 상관없이!)
handleClick()의 반환값(이 예시에서는 undefined, 즉 아무것도 없음)을 가져와서 onClick에 넣음
onClick={undefined}가 되어버려서, 정작 버튼을 클릭해도 아무 일도 안 일어나요. 왜냐하면 이미 렌더링될 때 다 실행되고, onClick에는 "클릭했을 때 뭘 할지"에 대한 정보가 아예 안 들어있으니까요.
{handleClick} | {handleClick()} | |
|---|---|---|
{ } 안의 정체 | 함수 자체(아직 실행 안함) | 함수를 실행한 결과값 |
| 언제 실행? | 클릭하는 순간 | 렌더링되는 순간(즉시) |
| 클릭 시 반응 | 정상 동작 ✅ | 아무 반응 없음 ❌ |
handleClick을 실행하는 게 아니에요. 리액트에게 이렇게 말하는 거예요: "이 버튼이 클릭되면 handleClick이라는 함수를 실행해." 즉 함수의 위치(참조)를 알려주는 것뿐이에요.()는 "지금 당장 실행해"라는 뜻이에요. 그래서 버튼이 화면에 나타나는 순간 이미 실행돼 버려요.
onClick={handleClick}. 반면 친구 앞에서 내가 바로 전화를 걸어버리는 것 = handleClick(). 번호만 건네야 하는데 전화를 먼저 걸어버린 셈이죠.function hello() { console.log("안녕"); }
const a = hello; // 함수 "자체"를 저장 → a 는 함수
const b = hello(); // 함수를 "실행한 결과"를 저장 → b 는 undefined
a는 함수고, b는 hello()가 실행된 결과예요. onClick도 똑같은 원리고요.
onClick={handleClick} ✅ 함수를 전달(참조) — 클릭할 때 실행. / onClick={handleClick()} ❌ 즉시 실행 — 누르기도 전에 이미 실행돼 버림.
function handleClick() {
alert("버튼이 눌렸어요!");
}
<button onClick={handleClick}>클릭</button>
// onClick → 클릭 이벤트를 감시하는 속성
// handleClick → 클릭이 나면 실행할 이벤트 핸들러
handleClick은 정해진 이름이 아니에요. function abc(){...}로 만들어 onClick={abc} 해도 똑같이 동작해요. 다만 handleClick·handleChange·handleSubmit처럼 handle을 붙이면 "아, 이벤트 처리 함수구나"를 바로 알 수 있어서 관례로 그렇게 써요.
이벤트가 일어나면 리액트는 핸들러의 첫 번째 인자로 이벤트 객체 e를 넘겨줘요(리액트에서는 브라우저 이벤트를 감싼 합성 이벤트(SyntheticEvent)예요). 이 e 안에 이벤트에 대한 정보가 들어 있어요.
e.target.value로 읽어요. e.target은 이벤트가 일어난 요소(여기선 input), .value는 그 요소의 현재 값이에요.
function SearchBox() {
function handleChange(e) {
console.log(e.target.value); // 입력된 글자
}
return <input onChange={handleChange} />;
}
form)을 제출하면 브라우저는 페이지를 새로고침하는 게 기본 동작이에요. SPA(페이지 전체를 새로고침하지 않고 화면만 바꾸는 방식, 리액트 앱이 이래요)에서는 이걸 원치 않으니 e.preventDefault()로 기본 동작을 막아요.
function Form() {
function handleSubmit(e) {
e.preventDefault(); // 새로고침 막기
console.log('제출 처리!');
}
return (
<form onSubmit={handleSubmit}>
<button>보내기</button>
</form>
);
}
e.target.value = 입력값 읽기, e.preventDefault() = 브라우저 기본 동작(새로고침·링크 이동 등) 막기.function add(a, b) {
return a + b;
}
const add = (a, b) => a + b;
() => handleClick(id)는 "아무것도 안 받고(()), handleClick(id)를 실행하는 새 함수"라는 뜻이에요.
핸들러에 특정 값(예: id)을 함께 넘기고 싶을 때가 있어요. 이때 onClick={handleClick(id)}라고 쓰면 앞에서 배운 것처럼 렌더 시 즉시 호출되어 버려요. 해결책은 화살표 함수로 감싸는 것이에요.
// ❌ 즉시 호출됨 — 클릭 전에 실행되어 버림
<button onClick={handleClick(id)}>삭제</button>
// ✅ 화살표로 감싸기 — 클릭할 때 handleClick(id) 실행
<button onClick={() => handleClick(id)}>삭제</button>
() => handleClick(id)는 "클릭하면 handleClick(id)를 실행하라"는 새 함수예요. 반면 handleClick(id)는 함수가 아니라 지금 실행한 결과값이라 클릭을 기다리지 않아요.
즉 "실행 방법을 담은 함수"를 넘기려고 화살표로 한 겹 감싸는 거예요.
onClick={() => 함수(인자)}. 인자가 없으면 그냥 onClick={함수}.동작이 아주 짧으면, 별도 함수를 선언하지 않고 화살표 함수를 그 자리(inline)에 바로 써도 돼요. 예를 들어 클릭할 때마다 카운트를 1 올리는 건 이렇게 쓸 수 있어요.
// state는 CH04에서 배웠어요 (useState)
<button onClick={() => setCount(count + 1)}>
+1
</button>
function handleXxx로 빼는 게 읽기 좋아요.
둘 다 결국 "onClick에 함수를 넘긴다"는 점은 똑같아요. 상황에 맞게 고르면 돼요.
setCount 같은 state 변경 함수를 호출해 화면을 바꾸는 게 리액트의 기본 패턴이에요. state는 CH04에서 다뤘어요.onClick·onChange·onSubmit.onClick={fn} ✅ / onClick={fn()} ❌(즉시 호출).e.target.value(입력값), e.preventDefault()(기본동작 막기).onClick={() => fn(id)}.