Event Handler 네이밍 컨벤션 in React


React를 사용하여 프로젝트를 개발하다 보면, 이벤트 핸들러를 정의하는 일이 빈번하게 발생하죠. 하지만 이때 이벤트 핸들러의 네이밍이 일관되지 않으면, 코드가 점점 복잡해지고 가독성이 떨어지는 문제가 발생하곤 해요. 특히, 협업하는 환경에서는 다른 개발자가 코드를 이해하기 어려워지고 유지 보수가 어려워질 가능성이 커져요.
이 글에서는 가장 많이 사용되는 Event Hanlder 네이밍 컨벤션을 소개해 드리고 왜 이러한 규칙으로 사용하고 있는지 그 이유에 대해 다뤄볼게요.
Props 네이밍 컨벤션
컴포넌트의 props로 이벤트 핸들러를 전달할 때에는 on*
접두사를 사용해요.
이는 React의 내장 이벤트 핸들러(onClick
, onChange
등)와 일관성을 유지하는 방식이며, 이 props가 이벤트 핸들러 역할을 한다는 것을 명확하게 나타낼 수 있어요.
동일한 이벤트가 단일인 경우
on[Event]
예시
function LikeButton({
onClick,
onMouseOver,
...rest
}: LikeButtonProps) {
return (
<button
type="button"
onClick={onClick}
onMouseOver={onMouseOver}
{...rest}
>
👍 좋아요
</button>
);
}
동일한 이벤트가 다중인 경우
만약 아래 예시의 ReactionButtonGroup
컴포넌트처럼 동일한 click 이벤트가 여러 개 존재하는 경우에는 네이밍을 어떻게 작성하는 것이 좋을까요?
이 상황에서는 on[Component][Event]
규칙을 따르는 것이 좋아요.
이렇게 하면 어떤 컴포넌트에서 발생하는 이벤트인지 명확하게 구분할 수 있어요.
on[Component][Event]
예시
function ReactionButtonGroup({
onLikeButtonClick,
onUnlikeButtonClick,
...rest
}: ReactionButtonGroupProps) {
return (
<div {...rest}>
<button type="button" onClick={onLikeButtonClick}>
👍 좋아요
</button>
<button type="button" onClick={onUnlikeButtonClick}>
👎 싫어요
</button>
</div>
);
}
Function 네이밍 컨벤션
이벤트 핸들러로 사용할 함수에는 handle*
접두사를 사용해요.
이렇게 작성하면 어떤 이벤트가 발생할 때 어떤 함수가 호출되는지 쉽게 유추할 수 있어요.
동일한 이벤트가 다일인 경우
handle[Event]
예시
function LoginButton() {
const handleLoginButtonClick = () => {
...
}
return (
<button type="button" onClick={handleLoginButtonClick}>
로그인
</button>
);
}
동일한 이벤트가 다중인 경우
만약 아래 예시의 LoginPage
컴포넌트처럼 동일한 click 이벤트가 여러 개 존재하는 경우에는 네이밍을 어떻게 작성하는 것이 좋을까요?
이 상황에서는 prop 네이밍 컨벤션 때와 비슷하게 handle[Component][Event]
을 따르는 것이 좋아요.
이렇게 하면 어떤 컴포넌트에서 어떤 이벤트가 발생될 때 호출되는 함수인지 명확하게 구분할 수 있어요.
handle[Component][Event]
예시
function LoginPage() {
const handleLoginButtonClick = () => {
...
}
const handleSignUpButtonClick = () => {
...
}
return (
<div>
...
<div>
<button type="button" onClick={handleLoginButtonClick}>
로그인
</button>
<button type="button" onClick={handleSignUpButtonClick}>
회원가입
</button>
</div>
</div>
);
}
비즈니스 로직과 UI 이벤트 분리하기
가끔 이런 생각이 들곤 하지 않나요?
💬 “굳이 핸들러 함수를 별도로 선언할 필요가 있을까?”
예를 들어, 아래와 같이 바로 비즈니스 로직을 바로 넣을 수도 있어요.
function LoginPage() {
const { login, signUp } = useAuth();
return (
<div>
...
<div>
<button type="button" onClick={login}>
로그인
</button>
<button type="button" onClick={signUp}>
회원가입
</button>
</div>
</div>
);
}
하지만 저는 핸들러 함수를 별도로 선언해서 onClick
prop에 주입하는 것을 추천해요. 그 이유는 바로 비즈니스 로직(login
)과 UI 이벤트(handleLoginButtonClick
)를 명확히 구분할 수 있기 때문이에요.
function LoginPage() {
const { login, signUp } = useAuth();
const handleLoginButtonClick = () => {
login();
}
const handleSignUpButtonClick = () => {
signUp();
}
return (
<div>
...
<div>
<button type="button" onClick={handleLoginButtonClick}>
로그인
</button>
<button type="button" onClick={handleSignUpButtonClick}>
회원가입
</button>
</div>
</div>
);
}
그런데 여기서 또 아래와 같은 의구심이 들 수 있어요.
💬 “비즈니스 로직과 UI 이벤트를 왜 명확히 구분해야 하지? 코드만 늘어나는 거 아니야?”
네 맞아요. 작성되는 코드는 더 늘어나요. 그렇지만 비즈니스 로직과 UI 이벤트를 분리하는 것은 유지 보수를 용이하게 해요.
프론트엔드 개발을 하다보면 로깅 처리를 요청받는 경우가 빈번하게 생기죠. 사용자의 행동을 추적하거나 특정 이벤트 발생 시 데이터를 수집해서 서비스 개선과 문제 해결에 중요한 역할을 하기도 하죠. 만약 비즈니스 로직을 바로 onClick
prop에 주입한 코드에서 위와 같은 요청을 받게 되면 아래와 같이 수정될 수 있어요.
function LoginPage() {
const { login, signUp } = useAuth();
const logger = useLogger();
return (
<div>
...
<div>
<button type="button" onClick={() => {
login();
logger.click('로그인_버튼');
}}>
로그인
</button>
<button type="button" onClick={() => {
signUp();
logger.click('회원가입_버튼');
}}>
회원가입
</button>
</div>
</div>
);
}
위와 같이 onClick
prop에 모든 함수 호출을 한꺼번에 넣는 것은 간단해 보일 수 있지만 다음과 같은 여러 단점이 있어요.
- 😵💫 가독성 저하
여러 함수 호출이 한 줄에 몰리면 코드의 가독성이 떨어져요.
특히 콜백 함수 안에 로직이 중첩되면서, 코드의 흐름을 파악하기 어려워져 유지 보수에 불리해요.
- 🛠️ 유지 보수 어려움
추후 기능 추가나 수정 시, 한곳에 몰려 있는 로직을 수정하는 과정에서 다른 기능에 영향을 줄 위험이 생겨요.
특히 로깅, 애니메이션 추가, 에러 처리 등 부가 기능이 점점 늘어날수록
onClick
내부가 매우 복잡해져요.
- 🔎 테스트 및 디버깅 어려움
각 함수의 동작을 독립적으로 테스트하거나 문제 발생 시 특정 기능만 디버깅하기가 어려워요.
onClick
내부에서 모든 로직이 실행되므로, 특정 로직이 실패할 때 전체 흐름이 영향을 받을 수 있어요.
- 📉 성능 문제
onClick={() => { … }}
형태의 인라인 함수는 리렌더링 시마다 새로운 함수를 생성해요. 물론 일반적인 arrow Function으로 작성된 핸들러 함수도 동일한 이슈가 있지만, 리렌더링 이슈를 방지하고 싶을 때에는useCallback
함수를 활용해 해결할 수 있는 해결책이 있어요.
그래서 위와 같은 단점을 해소하고자 아래 예시 코드와 같이 비즈니스 로직과 UI 이벤트 를 분리하여 작성하게 되면 로깅 기능 추가와 같은 부가 기능도 핸들러 함수 내에서 손쉽게 관리할 수 있어 기능 확장이 훨씬 수월해져요.
예를 들어, 나중에 로그인 버튼 클릭 시 추가 로깅이 필요한 경우에도 UI 이벤트 핸들러만 수정하면 되므로 전체 코드에 미치는 영향을 최소화할 수 있어요. 이러한 구조는 코드의 가독성을 높이고, 변경 사항이 발생할 때에도 안정적으로 대응할 수 있는 유연성을 제공해요.
function LoginPage() {
const { login, signUp } = useAuth();
const logger = useLogger();
const handleLoginButtonClick = () => {
login();
logger.click('로그인버튼');
}
const handleSignUpButtonClick = () => {
signUp();
logger.click('회원가입버튼');
}
return (
<div>
...
<div>
<button type="button" onClick={handleLoginButtonClick}>
로그인
</button>
<button type="button" onClick={handleSignUpButtonClick}>
회원가입
</button>
</div>
</div>
);
}
마무리
React 프로젝트에서 이벤트 핸들러 네이밍 컨벤션을 일관되게 적용하는 것은 코드의 가독성 유지 보수성을 높이는 중요한 개발 습관이에요. 하지만 이 글에서 소개한 네이밍 컨벤션과 구조가 절대적인 정답은 아니에요. 개발 환경, 팀의 코드 스타일, 프로젝트의 성격에 따라 더 적합한 방식이 존재할 수 있어요. 그리고 네이밍 컨벤션은 개발 과정에서의 선택지 중 하나일 뿐, 반드시 따라야 하는 절대적인 규칙은 아니에요. 중요한 것은 팀 전체가 일관된 방식을 이해하고 따름으로써, 코드의 유지 보수성을 높이고 협업 효율성을 극대화하는 것이에요.
‘✨ 자신과 팀에 가장 적합한 방식으로 컨벤션을 정의하고 적용해 보세요! 🚀’
참고 자료
Subscribe to my newsletter
Read articles from 박정환 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
