React 컴포넌트: 함수 선언문 vs 화살표 함수
Airbnb 스타일 가이드가 함수 선언문을 권장하는 이유와 실무에서의 선택 기준
읽는 데 27분
- #react
- #patterns
- #function-declaration
- #arrow-function
- #coding-style
이 문서의 목차
적용 환경: React 18+, TypeScript 5+
React 컴포넌트를 어떻게 선언하느냐를 두고 팀 내 논쟁이 생기곤 합니다. "화살표 함수가 더 간결하다"는 주장과 "함수 선언문이 더 명확하다"는 주장이 맞부딪히면서, 코드 리뷰에서 불필요한 스타일 논쟁으로 시간을 소비하게 됩니다. ESLint 규칙을 설정하려고 보니 react/function-component-definition 옵션이 있고, Airbnb 스타일 가이드를 참고하려고 보니 함수 선언문을 권장한다고 합니다.
왜 Airbnb는 함수 선언문을 권장하는 것일까요? 그리고 실무에서는 어떤 기준으로 선택해야 할까요?
먼저 두 방식의 문법적 차이를 살펴보겠습니다.
function Button({ children, onClick }: ButtonProps) {
return (
<button onClick={onClick}>
{children}
</button>
);
}const Button = ({ children, onClick }: ButtonProps) => {
return (
<button onClick={onClick}>
{children}
</button>
);
};겉보기에는 큰 차이가 없어 보이지만, JavaScript 엔진의 관점에서 이 두 방식은 몇 가지 중요한 차이가 있습니다.
| 특성 | 함수 선언문 | 화살표 함수 |
|---|---|---|
| 호이스팅 | O (정의 전 참조 가능) | X (TDZ 적용) |
| this 바인딩 | 동적 (호출 시점) | 정적 (정의 시점) |
| arguments 객체 | 사용 가능 | 사용 불가 |
| 이름 표시 | 항상 명시적 | 변수명으로 추론 |
React 컴포넌트에서 this나 arguments를 직접 사용하는 경우는 드물기 때문에, 실질적으로 중요한 차이는 호이스팅과 이름 표시 두 가지입니다.
Airbnb JavaScript 스타일 가이드는 React 컴포넌트를 정의할 때 함수 선언문을 권장합니다.
Airbnb React/JSX Style Guide
"If you don't have state or refs, prefer normal functions (not arrow functions) over classes. Arrow functions relying on function name inference is discouraged."
— 상태나 refs가 없다면 클래스보다 일반 함수를 사용하세요. 화살표 함수는 함수 이름 추론에 의존하므로 권장되지 않습니다.
가이드에서 제시하는 예시를 보면 그 의도가 명확합니다.
// ❌ 권장하지 않음 (function name inference에 의존)
const Listing = ({ hello }) => (
<div>{hello}</div>
);
// ✅ 권장
function Listing({ hello }) {
return <div>{hello}</div>;
}흥미로운 점은 2021년까지 Airbnb의 ESLint 설정(eslint-config-airbnb)이 오히려 함수 표현식을 강제했다는 것입니다. 문서에서는 함수 선언문을 권장하면서 ESLint 규칙은 반대로 설정되어 있었던 셈인데, 이 불일치는 GitHub Issue #2505 (새 창)를 통해 보고되었고 2021년 12월에 수정되었습니다.
Airbnb가 함수 선언문을 권장하는 데는 몇 가지 실질적인 이유가 있습니다.
함수 선언문은 호이스팅되기 때문에 파일 내에서 정의 위치에 관계없이 참조할 수 있습니다. 이 특성을 활용하면 컴포넌트를 파일 상단에 배치하고 헬퍼 함수를 하단에 두는 구조를 만들 수 있습니다. 코드를 읽는 사람이 파일을 열었을 때 가장 먼저 보게 되는 것이 핵심 컴포넌트가 되므로, 코드의 의도를 빠르게 파악할 수 있습니다.
// ✅ 함수 선언문: 헬퍼를 아래에 정의해도 문제 없음
function UserProfile({ userId }: Props) {
const user = useUser(userId);
return <ProfileCard data={formatUserData(user)} />;
}
function formatUserData(user: User): FormattedUser {
return {
displayName: `${user.firstName} ${user.lastName}`,
avatar: user.avatarUrl ?? DEFAULT_AVATAR,
joinDate: formatDate(user.createdAt),
};
}화살표 함수를 사용하면 TDZ(Temporal Dead Zone)로 인해 정의 전에 참조할 수 없습니다. 따라서 헬퍼 함수를 먼저 정의해야 하고, 파일을 읽는 사람은 핵심 컴포넌트를 찾기 위해 스크롤을 내려야 합니다.
// ❌ 화살표 함수: 헬퍼를 먼저 정의해야 함
const formatUserData = (user: User): FormattedUser => {
return {
displayName: `${user.firstName} ${user.lastName}`,
avatar: user.avatarUrl ?? DEFAULT_AVATAR,
joinDate: formatDate(user.createdAt),
};
};
const UserProfile = ({ userId }: Props) => {
const user = useUser(userId);
return <ProfileCard data={formatUserData(user)} />;
};에러가 발생했을 때 스택 트레이스에 함수 이름이 어떻게 표시되는지는 디버깅 효율성에 직접적인 영향을 미칩니다. 함수 선언문으로 정의된 함수는 항상 명시적인 이름을 가지므로, 스택 트레이스에서 정확히 어떤 컴포넌트에서 에러가 발생했는지 확인할 수 있습니다.
// 함수 선언문 사용 시 스택 트레이스
Error: Something went wrong
at UserProfile (UserProfile.tsx:15)
at ProfileCard (ProfileCard.tsx:8)
at App (App.tsx:22)
// 화살표 함수 + 구형 환경에서의 스택 트레이스
Error: Something went wrong
at Anonymous (UserProfile.tsx:15)
at Anonymous (ProfileCard.tsx:8)
at Anonymous (App.tsx:22)특히 프로덕션 환경에서 에러 추적 서비스(Sentry, DataDog 등)를 사용할 때, 함수 이름이 명확하게 표시되면 문제의 원인을 훨씬 빠르게 파악할 수 있습니다.
React DevTools에서 컴포넌트 트리를 확인할 때도 함수 이름이 중요합니다. 화살표 함수로 정의된 컴포넌트가 "Anonymous"로 표시되면, 복잡한 컴포넌트 구조에서 원하는 컴포넌트를 찾기가 어려워집니다. Profiler 탭에서 성능을 분석할 때도 마찬가지로, 익명 컴포넌트는 플레임 그래프에서 식별하기 어렵습니다.
물론 displayName 속성을 별도로 설정하면 이 문제를 해결할 수 있지만, 함수 선언문을 사용하면 추가 작업 없이 자동으로 이름이 표시됩니다.
// 화살표 함수 + displayName 설정
const Button = ({ children }: Props) => {
return <button>{children}</button>;
};
Button.displayName = "Button";
// 함수 선언문: 추가 설정 불필요
function Button({ children }: Props) {
return <button>{children}</button>;
}function 키워드는 "이것은 함수다"라는 강력한 시각적 신호를 줍니다. 파일 내에서 컴포넌트 정의를 찾을 때 function 키워드를 기준으로 빠르게 스캔할 수 있으며, 상수, 타입, 유틸리티 함수 등과 구분하기 쉽습니다.
팀 전체가 함수 선언문을 사용하면 코드베이스의 일관성이 높아지고, 새로운 팀원이 합류했을 때 코드를 이해하는 데 드는 시간이 줄어듭니다.
그렇다면 화살표 함수는 정말 피해야 할까요? 현대 JavaScript 환경에서는 반드시 그렇지만은 않습니다.
ECMAScript 2015(ES6) 이후, JavaScript 엔진은 변수에 할당된 함수의 이름을 자동으로 추론하도록 명세화되었습니다. 이를 name inference라고 합니다.
const Button = () => {};
console.log(Button.name); // "Button"최신 브라우저와 Node.js 런타임은 이 명세를 충실히 구현하고 있으며, Webpack이나 Vite 같은 번들러도 함수 이름을 올바르게 보존합니다. 따라서 대부분의 현대 환경에서는 화살표 함수를 사용해도 스택 트레이스나 DevTools에서 컴포넌트 이름이 정상적으로 표시됩니다.
name inference의 현실
대부분의 현대 번들러(Webpack, Vite)와 브라우저는 변수에 할당된 화살표 함수의 이름을 올바르게 추론합니다. 2024년 기준으로 "Anonymous" 문제를 실제로 마주치는 경우는 드뭅니다.
그럼에도 Airbnb가 함수 선언문을 권장하는 이유는, 명시적인 것이 암묵적인 것보다 낫다는 원칙 때문입니다. name inference는 "대부분" 동작하지만, 특정 빌드 설정이나 소스맵 구성에 따라 예상대로 동작하지 않을 수 있습니다. 함수 선언문은 이런 불확실성 없이 항상 명확한 이름을 가집니다.
결국 어떤 방식을 선택할지는 팀의 상황과 프로젝트 특성에 따라 달라집니다.
| 상황 | 권장 선택 | 이유 |
|---|---|---|
| 팀 컨벤션이 있는 경우 | 팀 컨벤션 따르기 | 일관성이 스타일 선택보다 중요 |
| 대규모 엔터프라이즈 | 함수 선언문 | 명확성, 디버깅 용이성 |
| 개인/소규모 프로젝트 | 선호도에 따라 | 실제 동작에 차이 없음 |
| Next.js 앱 라우터 | 기본 export는 화살표 | 프레임워크 관례 |
가장 중요한 것은 팀 내 일관성입니다. 함수 선언문이든 화살표 함수든, 팀 전체가 하나의 스타일을 따르는 것이 각자 선호하는 스타일을 섞어 쓰는 것보다 훨씬 낫습니다. 스타일 논쟁에 시간을 쏟는 대신, ESLint 규칙으로 강제하고 코드 리뷰에서는 로직에 집중하는 것이 바람직합니다.
팀의 스타일을 강제하려면 react/function-component-definition 규칙을 사용합니다.
{
"rules": {
"react/function-component-definition": [
"error",
{
"namedComponents": "function-declaration",
"unnamedComponents": "arrow-function"
}
]
}
}{
"rules": {
"react/function-component-definition": [
"error",
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
]
}
}namedComponents는 이름이 있는 컴포넌트(export되는 컴포넌트)에 적용되고, unnamedComponents는 인라인으로 정의되는 익명 컴포넌트에 적용됩니다. Airbnb 설정을 사용한다면 eslint-config-airbnb 버전 19.0.4 이상에서 함수 선언문이 기본값으로 설정되어 있습니다.
핵심 정리
- Airbnb 스타일 가이드는 함수 선언문을 권장하지만, 현대 환경에서 화살표 함수도 대부분의 문제가 해결되었습니다.
- 호이스팅이 필요하거나 디버깅 명확성이 중요하면 함수 선언문을 선택하세요.
- 팀 컨벤션이 가장 중요합니다. 일관성이 스타일 선택보다 우선합니다.
관련 문서
글쓴이 mirunamu00



