태그
목차

'use client'

생성일: 2024-02-04

수정일: 2024-02-04

Canary

'use client' 는 React 서버 컴포넌트를 지원하는 라이브러리를 사용하는 경우에만 필요하다.

'use client' 는 코드가 클라이언트에서 실행된다는 것을 표시(mark)한다.

레퍼런스

'use client'

파일 상단에 'use client' 를 추가하여 해당 모듈과 종속성을 클라이언트 코드로 표시한다.

'use client';

import { useState } from 'react';
import { formatDate } from './formatters';
import Button from './button';

export default function RichTextEditor({ timestamp, text }) {
  const date = formatDate(timestamp);
  // ...
  const editButton = <Button />;
  // ...
}

'use client' 로 마크된 파일을 서버 컴포넌트에 임포트 하면 번들러는 해당 모듈 임포트를 서버 코드와 클라이언트 코드 간의 바운더리로 처리한다.

RichTextEditor 의 종속성인 formatDateButton'use client' 지시문이 포함되어 있든 없든 클라이언트에서 실행된다. 즉 단일 모듈은 서버 코드에 임포트 될 때는 서버에서 실행되고 클라이언트 코드에 임포트 될 때는 클라이언트에서 실행된다.

주의사항

'use client' 지시어 작동 방식

React 앱에서 컴포넌트는 종종 별도의 파일 또는 모듈로 분할된다.

React 서버 컴포넌트를 사용하는 앱의 경우, 앱은 기본적으로 서버에서 렌더링 된다. 'use client' 는 모듈 종속성 트리에 서버-클라이언트 바운더리를 도입하여 효과적으로 클라이언트 모듈의 하위 트리를 생성한다.

이를 더 잘 이해하기 위해 다음 React 서버 컴포넌트 앱을 살펴보자:

// App.js
import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}

위의 앱 모듈 종속성 트리에서 InspirationGenerator.js'use client' 지시문은 해당 모듈과 해당 모듈의 모든 종속성을 클라이언트 모듈로 표시한다. 이제 InspirationGenerator.js 에서 시작하는 하위 트리가 클라이언트 모듈로 표시된다.

'use client'는 앱의 모듈 종속성 트리를 분할하여  와 그 모든 종속성을 클라이언트 렌더링으로 표시한다.
'use client'는 앱의 모듈 종속성 트리를 분할하여 InspirationGenerator.js 와 그 모든 종속성을 클라이언트 렌더링으로 표시한다.

렌더링하는 동안 프레임워크는 루트 컴포넌트를 서버에서 렌더링하고 렌더 트리를 계속 진행하면서 클라이언트 마크가 있는 코드는 렌더링에서 제외한다.

그런 다음 서버에서 렌더링된 렌더 트리의 일부가 클라이언트로 전송된다. 그러면 클라이언트 코드가 앞서 다운로드된 트리의 나머지 렌더링을 완료한다.

앱의 렌더 트리다.  와 그 자식 컴포넌트인  는 클라이언트 표시 코드에서 내보낸 컴포넌트이며 클라이언트 컴포넌트로 간주된다.
앱의 렌더 트리다. InspirationGenerator 와 그 자식 컴포넌트인 FancyText 는 클라이언트 표시 코드에서 내보낸 컴포넌트이며 클라이언트 컴포넌트로 간주된다.

정의를 내려보면 다음과 같다:

예제 앱을 살펴보면 App, FancyTextCopyright 는 모두 서버에서 렌더링되며 서버 컴포넌트로 간주된다. InspirationGenerator.js 와 그 종속성은 클라이언트 코드로 표시되므로, 컴포넌트 InspirationGenerator 와 그 하위 컴포넌트 FancyText 는 클라이언트 컴포넌트다.

FancyText가 클라이언트 컴포넌트이자 서버 컴포넌트인 이유

위의 정의에 따르면 FancyText 는 서버 컴포넌트이자 클라이언트 컴포넌트인데, 어떻게 그럴 수 있을까?

먼저 "컴포넌트"라는 용어가 그다지 정확하지 않다는 점을 알아야한다. 다음은 "컴포넌트"를 이해할 수 있는 두 가지 방법이다:

  1. "컴포넌트"는 컴포넌트의 정의를 말한다. 그리고 대부분의 경우 함수다.
// 컴포넌트 정의
function MyComponent() {
  return <p>My Component</p>;
}
  1. '컴포넌트'는 해당 컴포넌트의 사용을 지칭하기도 한다.
import MyComponent from './MyComponent';

function App() {
  // 컴포넌트 사용
  return <MyComponent />;
}

어떤 개념을 설명할 때 정확성은 중요하지 않은 경우가 많지만, 여기서는 중요다.

서버 컴포넌트나 클라이언트 컴포넌트에 대해 이야기할 때는 컴포넌트의 사용을 언급하는 것이다.

  • 컴포넌트가 모듈 내에서 'use client' 지시어를 사용하여 정의되었거나 다른 클라이언트 컴포넌트에서 호출되는 경우, "컴포넌트 사용"은 클라이언트 컴포넌트다.
  • 그렇지 않으면 "컴포넌트 사용"은 서버 컴포넌트다.
"컴포넌트 사용"을 나타내는 렌더 트리다.
"컴포넌트 사용"을 나타내는 렌더 트리다.

FancyText 에 대한 질문으로 돌아가서, 컴포넌트 정의에 'use client' 지시어가 없고 두 가지 용도가 있다는 것을 알 수 있다.

App 의 자식으로 FancyText 를 사용하면 해당 사용은 서버 컴포넌트로 표시된다. FancyText 를 가져와서 InspirationGenerator 에서 호출하는 경우, InspirationGenerator'use client' 지시문이 포함되어 있으므로 해당 사용은 클라이언트 컴포넌트다.

즉, FancyText 에 대한 "컴포넌트 정의"는 서버에서 평가되고 클라이언트에서 다운로드하여 클라이언트 "컴포넌트 사용"을 렌더링한다.

Copyright가 서버 컴포넌트인 이유

Copyright 는 클라이언트 컴포넌트인 InspirationGenerator 의 하위 컴포넌트로 렌더링되기 때문에 서버 컴포넌트라는 사실에 놀랄 수도 있다.

'use client' 는 렌더 트리가 아닌 모듈 종속성 트리에서 서버와 클라이언트 코드 사이의 바운더리를 정의한다는 점을 기억해야 한다.

 모듈 종속성 트리에서 서버 컴포넌트와 클라이언트 컴포넌트의 바운더리를 정의한다.
'use client' 모듈 종속성 트리에서 서버 컴포넌트와 클라이언트 컴포넌트의 바운더리를 정의한다.

모듈 종속성 트리에서 App.jsCopyright.js 모듈에서 Copyright 컴포넌트를 가져와 호출하는 것을 볼 수 있다. Copyright.js 에는 'use client' 지시어가 포함되어 있지 않으므로 컴포넌트 사용은 서버에서 렌더링 된다. App은 루트 컴포넌트이므로 서버에서 렌더링 된다.

클라이언트 컴포넌트는 JSX를 프로퍼티로 전달할 수 있으므로 서버 컴포넌트를 렌더링할 수 있다. 이 경우 InspirationGeneratorCopyright 를 자식으로 받는다. 그러나 InspirationGenerator 모듈은 Copyright 모듈을 직접 임포트하거나 컴포넌트를 호출하지 않으며, 이 모든 작업은 App 에서 수행된다. 실제로 Copyright 컴포넌트는 InspirationGenerator 가 렌더링을 시작하기 전에 완전히 실행된다.

요점은 컴포넌트 간의 부모-자식 렌더링 관계가 동일한 렌더링 환경을 보장하지 않는다는 것이다.

'use client'를 사용해야 하는 경우

'use client' 를 사용하면 컴포넌트가 클라이언트 컴포넌트인 경우를 결정할 수 있다. 서버 컴포넌트가 기본값이므로 클라이언트 렌더로 표시해야 하는 시점을 결정하기 위해 서버 컴포넌트의 장점과 한계를 간략하게 살펴본다.

간단하게 설명하기 위해 서버 컴포넌트에 대해 이야기하지만, 서버에서 실행되는 앱의 모든 코드에도 동일한 원칙이 적용된다.

서버 컴포넌트의 장점

서버 컴포넌트의 한계

서버 컴포넌트가 리턴하는 직렬화 가능 유형

다른 React 앱과 마찬가지로 부모 컴포넌트는 데이터를 자식 컴포넌트에 전달한다. 서로 다른 환경에서 렌더링되기 때문에 서버 컴포넌트에서 클라이언트 컴포넌트로 데이터를 전달할 때는 추가적인 고려가 필요하다.

서버 컴포넌트에서 클라이언트 컴포넌트로 전달되는 프로퍼티 값은 직렬화가 가능해야 한다.

연재 가능한 프로퍼티는 다음과 같다:

특히, 다음과 같은 기능은 지원되지 않는다:

사용법

상호 작용 및 상태가 있는 빌드

// App.js

'use client';

import { useState } from 'react';

export default function Counter({ initialValue = 0 }) {
  const [countValue, setCountValue] = useState(initialValue);
  const increment = () => setCountValue(countValue + 1);
  const decrement = () => setCountValue(countValue - 1);
  return (
    <>
      <h2>Count Value: {countValue}</h2>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </>
  );
}

Counter 는 값을 증가시키거나 감소시키기 위해 useState Hook과 이벤트 핸들러가 모두 필요하므로, 이 컴포넌트는 클라이언트 컴포넌트여야 하며 상단에 'use client' 지시어가 있어야 한다.

반대로 상호작용이 없는 UI 컴포넌트는 클라이언트 컴포넌트일 필요가 없다.

import { readFile } from 'node:fs/promises';
import Counter from './Counter';

export default async function CounterContainer() {
  const initialValue = await readFile('/path/to/counter_value');
  return <Counter initialValue={initialValue} />;
}

예를 들어, Counter 의 부모 컴포넌트인 CounterContainer 는 인터랙티브하지 않고 상태를 사용하지 않으므로 'use client' 가 필요하지 않다. 또한 CounterContainer 는 서버의 로컬 파일 시스템에서 데이터를 읽기 때문에 서버 컴포넌트여야 하며, 이는 서버 컴포넌트에서만 가능하다.

서버 또는 클라이언트 전용 기능을 사용하지 않고 렌더링 위치에 구애받지 않는 컴포넌트도 있다. 앞선 예시에서 FancyText 는 이러한 컴포넌트 중 하나다.

export default function FancyText({ title, text }) {
  return title ? (
    <h1 className="fancy title">{text}</h1>
  ) : (
    <h3 className="fancy cursive">{text}</h3>
  );
}

이 경우 'use client' 지시문을 추가하지 않으므로 서버 컴포넌트에서 참조할 때 소스 코드가 아닌 FancyText 의 출력이 브라우저로 전송된다. 앞의 Inspirations 앱 예시에서 설명한 것처럼 FancyText 는 가져와서 사용하는 위치에 따라 서버 또는 클라이언트 컴포넌트로 사용된다.

그러나 FancyText 의 HTML 출력이 소스 코드(종속성 포함)에 비해 큰 경우, 항상 클라이언트 컴포넌트가 되도록 강제하는 것이 더 효율적일 수 있다. 예를 들어 긴 SVG 경로 문자열을 반환하는 컴포넌트는 컴포넌트를 클라이언트 컴포넌트로 강제 설정하는 것이 더 효율적일 수 있다.

클라이언트 API 사용하기

React 앱은 웹 저장소, 오디오 및 비디오 조작, 디바이스 하드웨어를 위한 브라우저의 API와 같은 클라이언트별 API를 사용할 수 있다.

이 예시에서 컴포넌트는 canvas 엘리먼트를 조작하기 위해 DOM API를 사용한다. 이러한 API는 브라우저에서만 사용할 수 있으므로 클라이언트 컴포넌트로 표시해야 한다.

'use client';

import { useRef, useEffect } from 'react';

export default function Circle() {
  const ref = useRef(null);
  useLayoutEffect(() => {
    const canvas = ref.current;
    const context = canvas.getContext('2d');
    context.reset();
    context.beginPath();
    context.arc(100, 75, 50, 0, 2 * Math.PI);
    context.stroke();
  });
  return <canvas ref={ref} />;
}

써드파티 라이브러리 사용하기

React 앱에서는 일반적인 UI 패턴이나 로직을 처리하기 위해 서드파티 라이브러리를 활용하는 경우가 많다.

이러한 라이브러리는 컴포넌트 Hook이나 클라이언트 API에 의존할 수 있다. 다음 React API 중 하나를 사용하는 서드파티 컴포넌트는 클라이언트에서 실행되어야 한다:

이러한 라이브러리가 React Server 컴포넌트와 호환되도록 업데이트된 경우, 이미 자체적으로 'use client' 지시어가 포함되어 있으므로 서버 컴포넌트에서 직접 사용할 수 있다. 라이브러리가 업데이트되지 않았거나 컴포넌트에 클라이언트에서만 지정할 수 있는 이벤트 핸들러와 같은 프로퍼티가 필요한 경우, 서드파티 클라이언트 컴포넌트와 사용하려는 서버 컴포넌트 사이에 자체 클라이언트 컴포넌트 파일을 추가해야 할 수 있다.