iBetter Books
수정

Ch 06. 직렬화의 벽 — 함수와 클래스를 prop으로 넘길 수 없는 이유

편지로 보낼 수 없는 것들

서버에서 클라이언트로 데이터를 전달할 때, 그 데이터는 반드시 "전송 가능한 형태"여야 합니다. 마치 편지처럼요. 편지에는 문자로 표현할 수 있는 것만 담을 수 있습니다. 냄새나 질감을 편지로 보낼 수는 없습니다.

서버 컴포넌트가 클라이언트 컴포넌트에 prop을 전달할 때도 같습니다. 그 데이터는 직렬화(serialize) 가능해야 합니다. JSON으로 변환할 수 있어야 한다는 뜻입니다.

직렬화가 불가능한 것들

다음 값들은 JSON으로 표현할 수 없습니다.

함수(Function). JSON.stringify(() => {}) 를 해보면 undefined가 됩니다. 함수는 코드이지 데이터가 아니기 때문입니다.

클래스 인스턴스. new MyClass()로 만든 객체는 JSON으로 직렬화하면 메서드가 사라집니다.

Date 객체. new Date()는 JSON으로 변환하면 문자열이 됩니다. 받는 쪽에서 다시 new Date()로 변환해야 합니다.

Map, Set. 이들도 JSON으로 직렬화되지 않습니다.

실수 예시와 에러 메시지

가장 흔한 실수는 Server Component에서 Client Component로 이벤트 핸들러 함수를 넘기려는 것입니다.

// 파일: app/page.tsx
// Server Component
import Button from '@/components/Button';

export default function Page() {
  // ❌ 이렇게 하면 에러가 납니다!
  function handleClick() {
    console.log('클릭!');
  }

  return <Button onClick={handleClick} />;
}
```text

이 코드를 실행하면 다음과 같은 에러가 납니다.

Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".

함수는 직렬화할 수 없으므로, Server Component에서 Client Component의 이벤트 핸들러 prop으로 함수를 넘길 수 없습니다.### 해결 방법 — Children 패턴가장 우아한 해결책은 Children 패턴입니다. 이벤트 핸들러가 필요한 로직을 Client Component 안으로 옮깁니다.```tsx// 파일: app/components/Button.tsx// Client Component에서 이벤트를 직접 처리'use client';export default function Button({ label }: { label: string }) {  function handleClick() {    console.log('클릭!');  }  return <button onClick={handleClick}>{label}</button>;}```text```tsx// 파일: app/page.tsx// Server Componentimport Button from '@/components/Button';export default function Page() {  return <Button label="클릭하세요" />; // ✅ 문자열은 직렬화 가능}```text함수 자체를 넘기는 대신, 함수가 필요한 컴포넌트 내부에서 정의합니다.### 넘길 수 있는 것들반대로 직렬화 가능한 것들은 자유롭게 넘길 수 있습니다.```tsx// 파일: app/posts/page.tsx// Server Componentimport PostCard from '@/components/PostCard'; // Client Componentinterface Post {  id: number;  title: string;  excerpt: string;  publishedAt: string; // Date를 string으로 변환해서 전달}export default async function PostsPage() {  const posts: Post[] = await fetch('https://api.example.com/posts').then(    (r) => r.json()  );  return (    <div>      {posts.map((post) => (        // ✅ 숫자, 문자열, 객체(직렬화 가능한), 배열은 prop으로 전달 가능        <PostCard          key={post.id}          id={post.id}          title={post.title}          excerpt={post.excerpt}          publishedAt={post.publishedAt} // string으로 이미 변환됨        />      ))}    </div>  );}

Date 객체는 서버에서 .toISOString() 또는 .toLocaleDateString() 등으로 문자열로 변환한 후 전달합니다.

이 파트를 마치며

Server Components는 처음에는 낯설지만, 일단 "서버에서 할 일"과 "클라이언트에서 할 일"을 구분하는 사고방식이 자리 잡히면 자연스러워집니다. 주방에서 음식을 준비하고, 홀에서 손님과 대화한다 — 이 비유를 기억하면 됩니다.

다음 파트에서는 데이터를 가져오는 다양한 방법을 집중적으로 다룹니다. fetch, 캐시 전략, Server Actions, Route Handlers, 그리고 Prisma로 실제 데이터베이스에 연결하는 방법까지 배웁니다.