Next.js를 활용한 프로젝트를 진행하면서 App Router 환경에서 컴포넌트를 작성할 때 함수 선언 방식과 화살표 함수 방식 중 어떤 것을 선택해야 하는지 고민한 경험이 있을 것입니다. 여러 프로젝트와 코드 리뷰를 거치면서 제가 얻은 인사이트를 공유하고자 합니다. 결론부터 말씀드리자면, 둘 다 사용해도 괜찮습니다. 그러나 이 주제에 대해 좀 더 깊이 알아보도록 하겠습니다.
Next.js 공식 문서에서는 어떻게 표현하고 있는가?
Next.js의 공식 문서를 살펴보면 두 가지 방식 모두 예제로 제시하고 있습니다. 공식 문서의 예제에서는 주로 함수 선언식을 사용하고 있지만, 화살표 함수를 사용한 예제도 종종 볼 수 있습니다.
함수 선언식 예제:
// app/page.js
export default function Page() {
return (
<div>
<h1>Hello, Next.js!</h1>
</div>
)
}
화살표 함수 예제:
// app/components/Button.js
const Button = () => {
return (
<button>Click me</button>
)
}
export default Button
Next.js 팀은 어느 한 쪽을 특별히 권장하지 않으며, 개발자의 선호도나 팀의 코딩 컨벤션에 따라 선택할 수 있도록 열어두고 있습니다.
실제 프로젝트에서는 어떤 방식이 더 많이 사용되고 있는가?
다양한 오픈 소스 프로젝트와 실무 환경에서의 코드를 분석해 본 결과, 페이지 컴포넌트(page.js, layout.js 등)에서는 함수 선언식을 더 많이 사용하는 경향이 있고, 일반 컴포넌트에서는 화살표 함수를 사용하는 비율이 높았습니다.
이는 Next.js의 App Router가 도입된 초기에 함수 선언식으로 작성된 예제가 많았던 영향도 있습니다. 또한 서버 컴포넌트의 경우 함수 선언식을 사용하면 코드의 가독성이 높아진다는 의견도 있습니다.
// 페이지 컴포넌트에서 주로 사용되는 함수 선언식
export default function ProductPage({ params }) {
return (
<div>
<h1>Product: {params.id}</h1>
<ProductDetails id={params.id} />
</div>
)
}
// 일반 컴포넌트에서 자주 볼 수 있는 화살표 함수
const ProductCard = ({ product }) => {
return (
<div className="card">
<h3>{product.name}</h3>
<p>{product.description}</p>
<span>{product.price}원</span>
</div>
)
}
export default ProductCard
개발자들의 견해는 어떠한가?
React와 Next.js 커뮤니티의 개발자들 사이에서도 이 주제에 관한 의견은 다양합니다. 몇 가지 주요 의견을 정리해보았습니다:
- 가독성 측면: 함수 선언식은 컴포넌트의 이름을 명확히 알 수 있어 디버깅과 에러 트레이싱에 유리하다는 의견이 있습니다.
- 일관성 측면: 프로젝트 전체에서 하나의 스타일을 유지하는 것이 코드 리뷰와 유지보수에 도움이 된다는 의견이 많습니다.
- 호이스팅(Hoisting): 함수 선언식은 호이스팅되기 때문에 코드의 순서에 덜 민감하다는 장점이 있습니다.
- this 바인딩: 화살표 함수는 자체적인 this를 가지지 않아 부모 스코프의 this를 사용하므로 이벤트 핸들러 등에서 유용할 수 있습니다.
제 경험에 비추어 볼 때, 대규모 프로젝트에서는 팀 내 코드 컨벤션을 정하고 일관성 있게 적용하는 것이 가장 중요했습니다. 개인적으로는 페이지 컴포넌트와 레이아웃에는 함수 선언식을, 재사용 가능한 UI 컴포넌트에는 화살표 함수를 사용하는 방식을 선호합니다.
초기 버그와 현재 상황
Next.js App Router가 처음 도입되었을 때는 화살표 함수를 사용한 서버 컴포넌트에서 간혹 문제가 발생하는 사례가 보고되었습니다. 이로 인해 많은 개발자들이 함수 선언식을 기본으로 사용하게 되었습니다.
그러나 현재 Next.js 13.4 버전 이후로는 이러한 문제가 대부분 해결되어, 두 방식 모두 안정적으로 사용할 수 있게 되었습니다. 실제로 제가 진행한 여러 프로젝트에서 두 방식을 혼용해서 사용했지만 특별한 문제가 발생하지 않았습니다.
// 서버 컴포넌트에서의 비동기 데이터 fetching - 함수 선언식
export default async function ProductList() {
const products = await fetchProducts();
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
)
}
// 서버 컴포넌트에서의 비동기 데이터 fetching - 화살표 함수
const ProductList = async () => {
const products = await fetchProducts();
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
)
}
export default ProductList
두 방식 모두 현재는 정상적으로 작동합니다.
함수 선언식이 더 많이 사용되는 이유는?
실무 프로젝트와 오픈 소스 코드를 분석해보면, 특히 App Router 환경에서는 함수 선언식이 더 자주 사용되는 경향이 있습니다. 이에 대한 주요 이유는 다음과 같습니다:
- 공식 문서의 영향: Next.js 공식 문서와 튜토리얼에서 주로 함수 선언식을 사용한 예제를 제공하기 때문에 많은 개발자들이 이를 따르고 있습니다.
- 명시적인 네이밍: 페이지 컴포넌트의 경우 함수 선언식을 사용하면 'Page', 'Layout' 등의 명확한 이름을 통해 해당 파일의 역할을 직관적으로 알 수 있습니다.
- 초기 안정성 이슈: 앞서 언급했듯이 초기 버전에서의 안정성 문제로 인해 많은 개발자들이 함수 선언식을 선호하게 되었고, 이러한 습관이 이어지고 있습니다.
- 디버깅의 용이성: React DevTools에서 함수 선언식으로 작성된 컴포넌트는 해당 함수의 이름을 그대로 표시하므로 디버깅 시 컴포넌트를 쉽게 식별할 수 있습니다.
실제 프로젝트에서 우리 팀은 다음과 같은 규칙을 적용했습니다:
// 규칙 1: 페이지, 레이아웃, 라우트 핸들러에는 함수 선언식 사용
// app/dashboard/page.js
export default function DashboardPage() {
// ...
}
// 규칙 2: 재사용 컴포넌트에는 화살표 함수 사용
// components/ui/Button.js
const Button = ({ children, onClick }) => {
// ...
return <button onClick={onClick}>{children}</button>
}
export default Button
이러한 방식을 통해 코드베이스 전반에 걸쳐 일관성을 유지하면서도, 각 파일의 역할에 맞는 적절한 함수 표현 방식을 선택할 수 있었습니다.
결론
Next.js App Router 환경에서 함수 선언식과 화살표 함수 중 어떤 것을 선택할지는 궁극적으로 개발자나 팀의 선호도에 달려 있습니다. 기술적인 측면에서는 두 방식 모두 현재 안정적으로 작동하므로, 다음 요소들을 고려하여 선택하시면 됩니다:
- 팀의 코딩 컨벤션과 일관성
- 코드 가독성과 유지보수성
- 디버깅의 용이성
- 기존 코드베이스의 스타일
제 경험상, 프로젝트의 구조와 각 파일의 역할에 따라 적절한 방식을 선택하는 것이 가장 실용적인 접근법이었습니다. 페이지, 레이아웃과 같은 핵심 라우팅 파일에는 함수 선언식을, 재사용 가능한 UI 컴포넌트에는 화살표 함수를 사용하는 방식이 실무에서 효과적이었습니다.
어떤 방식을 선택하든, 일관성을 유지하고 팀 내에서 명확한 가이드라인을 설정하는 것이 장기적인 프로젝트 관리에 도움이 될 것입니다. 결국은 "어떤 것이 옳은가"보다 "어떤 것이 우리 팀과 프로젝트에 적합한가"를 고려하여 결정하는 것이 중요합니다.
Comments
Post a Comment