본문 바로가기

Next.js

Next.js 10-2 React Essential 패턴

 

  • 클라이언트 컴포넌트를 리프로 이동하기

  애플리케이션의 성능을 개선하려면 가능한 경우 클라이언트 컴포넌트를 컴포넌트 트리의 리프로 이동하는 것이 좋다. 예를 들어 정적 요소(예: 로고, 링크 등)가 있는 레이아웃과 상태를 사용하는 대화형 검색 표시줄이 있을 수 있다. 전체 레이아웃을 클라이언트 컴포넌트로 만드는 대신 대화형 논리를 클라이언트 컴포넌트(예: <SearchBar />)로 이동하고 레이아웃을 서버 컴포넌트로 유지한다. 이는 레이아웃의 모든 컴포넌트 JavaScript를 클라이언트에 보낼 필요가 없음을 의미한다.

// SearchBar is a Client Component
import SerchBar from './serchbar';
// Logo is a Server Component
import Logo from './logo'

// Layout is a Server Component by default
export default function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>
      <main>{children}</main>
    </>
  )
}
  • 클라이언트 및 서버 컴포넌트 구성

  서버 및 클라이언트 컴포넌트는 동일한 컴포넌트 트리에서 결합될 수 있다. 활동 뒤에서 React는 다음과 같이 렌더링을 처리한다. 먼저 서버에서 React는 결과를 클라이언트에 보내기 전에 모든 서버 컴포넌트를 렌더링 한다. 여기에는 클라이언트 컴포넌트 내에 중첩된 서버 컴포넌트가 포함되고, 이 단계에서 발생한 클라이언트 컴포넌트는 건너뛴다.

  다음으로 클라이언트에서 React 컴포넌트의 렌더링 된 결과에서 클라이언트 컴포넌트와 슬롯을 렌더링 하여 서버와 클라이언트에서 수행된 작업을 병합한다. 서버 컴포넌트가 클라이언트 컴포넌트 내에 중첩된 경우 렌더링 된 콘텐츠는 클라이언트 컴포넌트 내에 올바르게 배치된다. 

  위에서 설명한 렌더링 흐름을 고려할 때 서버 컴포넌트를 클라이언트 컴포넌트로 가져오는 데에는 제한이 있다. 이 접근 방식에는 추가 서버 왕복이 필요하기 때문이다. 다음과 같은 패턴은 서버 컴포넌트를 클라이언트 컴포넌트로 가져올 수 없다.

'use client';

// This pattern will **not** work!
// You cannot import a Server Component into a Client Component
import ExampleServerComponent from './example-server-component';

export default function ExampleClientComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);
  
  return (
    <>
      <button onClick={() => setCount(count+1)}>{count}</button>
      
      <ExampleServerComponent />
    </>
  )
}

  위 대신 클라이언트 컴포넌트를 디자인할 때 React props를 사용하여 서버 컴포넌트의 "구멍"을 표시할 수 있다. 서버 컴포넌트는 서버에서 렌더링 되고 클라이언트 컴포넌트가 클라이언트에서 렌더링 될 때 "구멍"은 서버 컴포넌트의 렌더링 된 결과로 채워진다. 일반적인 패턴은 React children props를 사용하여 "구멍"을 만드는 것이다.  <ExampleClientComponent>를 리팩터링 하여 일반 하위 props를 수락하고 <ExampleClientComponent>의 import 및 명시적 중첩을 상위 컴포넌트로 이동하게 할 수 있다.

import { useState } from 'react';

export default function ExampleClientComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);
  
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      
      {children}
    </>
  )
}

  <ExampleClientComponent>는 children이 무엇인지 알지 못한다. 사실, 그것의 관점에서 children이 결국 서버 컴포넌트의 결과로 채워질 것이라는 사실조차 모른다. 이제 ExampleClientComponent가 가진 유일한 책임은 children이 배치될 위치를 결정하는 것이다.

  그리고 아래와 같이하면 상위 서버 컴포넌트에서 <ExampleClientComponent> 및 <ExampleServerComponent>를 모두 가져오고 <ExampleServerComponent>를 <ExampleClientComponent>의 하위로 전달할 수도 있다.

// This pattern work:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ExampleClientComponent from './example-client-component'
import ExampleServerComponent from './example-server-component'

// Pages in Next.js are Server Components by default
export default function Page() {
  return (
    <ExampleClientComponent>
      <ExampleServerComponent />
    </ExampleClientComponent>
  )
}

  이 접근 방식을 사용하면 <ExampleClientComponent> 및 <ExampleServerComponent>의 렌더링이 분리되고 클라이언트 컴포넌트보다 먼저 서버에서 렌더링 되는 서버 컴포넌트와 정렬하여 독립적으로 렌더링 할 수 있다.