Серверные и клиентские компоненты

Чтобы понять, как работают серверные и клиентские компоненты, полезно ознакомиться с двумя фундаментальными концепциями веб-разработки:

Серверная и клиентская среды

В контексте веб-приложений:

Диаграмма, показывающая браузер слева и сервер справа, разделенные сетевой границей.
  • Клиент — это браузер на устройстве пользователя, который отправляет запрос серверу для получения кода вашего приложения. Затем он преобразует полученный от сервера ответ в интерфейс, с которым может взаимодействовать пользователь.
  • Сервер — это компьютер в дата-центре, который хранит код вашего приложения, получает запросы от клиента, выполняет вычисления и отправляет соответствующий ответ.

Каждая среда имеет свои возможности и ограничения. Например, перенеся рендеринг и получение данных на сервер, вы можете уменьшить объем кода, отправляемого клиенту, что может улучшить производительность вашего приложения. Но, как вы узнали ранее, чтобы сделать пользовательский интерфейс интерактивным, вам необходимо обновлять DOM на клиенте.

Поэтому код, который вы пишете для сервера и клиента, не всегда одинаков. Некоторые операции (например, получение данных или управление состоянием пользователя) лучше подходят для одной среды, чем для другой.

Сетевая граница

Сетевая граница — это концептуальная линия, разделяющая разные среды.

В React вы сами выбираете, где разместить сетевую границу в дереве компонентов. Например, вы можете получать данные и рендерить посты пользователя на сервере (используя серверные компоненты), а затем рендерить интерактивную кнопку LikeButton для каждого поста на клиенте (используя клиентские компоненты).

Аналогично, вы можете создать компонент Nav, который рендерится на сервере и используется на нескольких страницах, но если вы хотите показать активное состояние для ссылок, вы можете отрендерить список Links на клиенте.

Дерево компонентов, показывающее макет с тремя дочерними компонентами: Nav, Page и Footer. Компонент Page имеет двух дочерних компонентов: Posts и LikeButton. Компонент Posts рендерится на сервере, а компонент LikeButton — на клиенте.

За кулисами компоненты разделяются на два графа модулей. Серверный граф модулей (или дерево) содержит все серверные компоненты, которые рендерятся на сервере, а клиентский граф модулей (или дерево) содержит все клиентские компоненты.

После рендеринга серверных компонентов на клиент отправляется специальный формат данных под названием React Server Component Payload (RSC). Полезная нагрузка RSC содержит:

  1. Результат рендеринга серверных компонентов.
  2. Заполнители (или "дыры") для мест, где должны быть отрендерены клиентские компоненты, и ссылки на их JavaScript-файлы.

React использует эту информацию для объединения серверных и клиентских компонентов и обновления DOM на клиенте.

Давайте посмотрим, как это работает.

Использование клиентских компонентов

Как вы узнали в предыдущей главе, Next.js по умолчанию использует серверные компоненты — это улучшает производительность вашего приложения и означает, что вам не нужно предпринимать дополнительные шаги для их использования.

Возвращаясь к ошибке в вашем браузере, Next.js предупреждает вас о том, что вы пытаетесь использовать useState внутри серверного компонента. Вы можете исправить это, перенеся интерактивную кнопку "Like" в клиентский компонент.

Создайте новый файл like-button.js внутри папки app, который экспортирует компонент LikeButton:

/app/like-button.js
export default function LikeButton() {}

Перенесите элемент <button> и функцию handleClick() из page.js в ваш новый компонент LikeButton:

/app/like-button.js
export default function LikeButton() {
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

Затем перенесите состояние likes и импорт:

/app/like-button.js
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

Теперь, чтобы сделать LikeButton клиентским компонентом, добавьте директиву React 'use client' в начало файла. Это указывает React рендерить компонент на клиенте.

/app/like-button.js
'use client';
 
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

Вернитесь в файл page.js и импортируйте компонент LikeButton на вашу страницу:

/app/page.js
import LikeButton from './like-button';
 
function Header({ title }) {
  return <h1>{title ? title : 'Default title'}</h1>;
}
 
export default function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
 
  return (
    <div>
      <Header title="Develop. Preview. Ship." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <LikeButton />
    </div>
  );
}

Сохраните оба файла и откройте ваше приложение в браузере. Теперь, когда ошибок нет, после внесения изменений и сохранения вы должны заметить, что браузер автоматически обновляется, отражая изменения.

Эта функция называется Быстрое обновление (Fast Refresh). Она обеспечивает мгновенную обратную связь по любым изменениям и предустановлена в Next.js.

Итоги

Подводя итоги, вы узнали о серверной и клиентской средах и о том, когда использовать каждую из них. Вы также узнали, что Next.js по умолчанию использует серверные компоненты React для повышения производительности, и как можно использовать клиентские компоненты для небольших интерактивных частей вашего интерфейса.

Дополнительное чтение

Существует гораздо больше информации о серверных и клиентских компонентах. Вот некоторые дополнительные ресурсы: