Содержание

В последнее время было много шумихи и волнений по поводу серверных компонентов React. Это связано с тем, что серверные компоненты React позволяют разработчикам передавать задачи, связанные с компонентами, на сервер. Это избавляет от необходимости распространять пакетный JavaScript и внешние API-запросы для гидратации компонента, а также исключает сценарии, которые приведут к увеличению задержек в клиентском приложении.
В этой статье мы поговорим о том, что такое серверные компоненты React, а также о том, как использовать их при создании приложений.
Что такое серверные компоненты?
Серверный компонент - это новая функция, появившаяся в React 18 и используемая по умолчанию в Next.js 13. Серверный компонент - это, по сути, тип компонента, который получает данные с сервера и отображает их на сервере. Впоследствии эти данные передаются в клиентское приложение в формате, который клиентское приложение может отобразить.
Серверные компоненты отображаются в пользовательском формате, не имеющем стандартного протокола, но напоминающем формат JSON. DOM react распознает этот формат и отображает его соответствующим образом, когда он распознан.
Постановка проблемы, которая ввела идею серверных компонентов React
Мы создадим сценарий, который представит идею серверных компонентов.
Мы можем структурировать страницу товара следующим образом:
const ProductPage = ({ productId })=> { return ( <> <ProductDetails productId={productId}> <ProductItem productId={productId} /> <MatchedItems productId={ productId } /> <ProductDetails> </> ) }
Теперь можно по-разному реализовать решение для получения данных для содержимого API, которое будет отображаться на этой странице. Мы можем получить данные для компонентов одним махом, как показано ниже:
const ProductPage = ({ productId })=> { const data = fetchContentsFromAPI(); return ( <> <ProductDetails details={data.details} productId={productId}> <ProductItem item={data.product} productId={productId} /> <MatchedItems items={data.matchedItems} productId={productId} /> <ProductDetails> </> ) }
Этот метод отлично работает и имеет свои преимущества, например:
Это может быть удобно для пользователей, поскольку все компоненты отображаются на клиенте после получения данных.
Однако это может создать некоторые проблемы, такие как:
Высокий уровень связанности, поскольку содержимое данных привязывается к дочерним компонентам родительского компонента. Это может сделать эти компоненты сложными в обслуживании. Это также противоречит идее единой ответственности, поскольку дочерние компоненты не несут индивидуальной ответственности за свои данные и поэтому зависят от данных родительского компонента. Высокое время загрузки, так как он получает все данные для всех компонентов сразу.
Для обеспечения единой ответственности мы могли бы перестроить родительский компонент так, чтобы он отображал компоненты следующим образом:
const ProductDetails = ({productId, children}) => { const details = fetchProductDetails(productId); return ( <> {{ children }} </> ) } const ProductItem = ({productId}) => { const item = fetchProductItem(productId); return (....) } const MatchedItems = ({productId}) => { const items = fetchMatchedItems(productId); return (...) } const ProductPage = ({ productId })=> { return ( <> <ProductDetails productId={productId}> <ProductItem productId={productId} /> <MatchedItems productId={productId} /> <ProductDetails> </> ) }
Этот метод отлично работает и имеет свои преимущества, например:
Единая ответственность: Каждый компонент отвечает за свои данные.
Однако это может создать некоторые проблемы, такие как:
Это может не соответствовать пользовательскому опыту, так как любой из дочерних компонентов может быть отображен на клиенте раньше другого на основе времени загрузки их API-вызовов, в результате чего пользователи будут видеть одну часть страницы раньше другой. Это также создаст сценарий сетевого водопада из-за последовательной выборки данных, поскольку компонент ProductDetails будет отображаться первым перед дочерними компонентами (ProductItem, MatchedItems).
У этих методов есть свои плюсы и минусы, однако есть одно общее для них ограничение. Это ограничение заключается в том, что оба метода требуют выполнения API-вызовов на сервер от клиента, что может создать ситуацию высокой задержки между клиентом и сервером.
Именно это ограничение изначально побудило команду React представить серверные компоненты: компоненты на сервере. Поскольку серверные компоненты существуют на сервере, они могут быстрее выполнять вызовы API и быстрее отрисовываться, чем компоненты, отрисованные на клиентской стороне приложения.
Изначально они были созданы для решения проблемы высокой задержки, но затем появились новые приложения. Поскольку компоненты располагаются на сервере, им был разрешен доступ к серверной инфраструктуре, что подразумевает возможность подключения к базам данных и выполнения запросов к ним.
Разница между серверными компонентами Reactкомпоненты и клиентские компоненты
Основное различие между серверными и клиентскими компонентами заключается в том, что в то время как серверные компоненты отображают компоненты на сервере, клиентские компоненты отображают компоненты на клиенте.
Обычно в реактивных приложениях на стороне клиента, когда пользователь запрашивает веб-страницу с сервера, сервер отвечает браузером страницей (файлом Javascript). Браузер загружает данные (Javascript-файл) и использует их для создания веб-страницы.
Для серверных компонентов не требуется отправка Javascript клиенту, что позволяет уменьшить пакет JavaScript, отправляемый клиенту. Клиентские компоненты, с другой стороны, отправляются клиенту и увеличивают размер пакета приложения (клиентский компонент - это типичный, традиционный компонент React).
Еще одно различие заключается в среде рендеринга, которая наделяет их различными свойствами, как описано ниже:
Серверный компонент не может использовать такие хуки React, как useState, useReducer, useEffect и т. д. Это происходит потому, что серверный компонент отображается на сервере и не имеет доступа к хукам, которые могут повлиять на DOM (объектную модель документа), которая существует только на клиенте. С другой стороны, клиентский компонент - это обычный React-компонент, который все еще имеет доступ к хукам. Серверный компонент не имеет доступа к API браузера, таким как SessionStorage, localStorage и т.д. С другой стороны, клиентский компонент - это обычный компонент React, который все еще имеет доступ к API браузера. Серверный компонент может использовать async/await для источников данных только для сервера, таких как базы данных, внутренние сервисы, файловые системы и так далее, в то время как клиентские компоненты не могут получить прямой доступ к источникам данных только для сервера.
Разница между серверными компонентами React и рендерингом на стороне сервера (SSR) в React.
Серверный рендеринг (SSR) в случае React означает способность приложения превращать компоненты React на сервере в полностью отрисованную статическую HTML-страницу для клиента.
Серверные компоненты React, с другой стороны, работают с SSR через промежуточную структуру (протокол, похожий на формат JSON), чтобы обеспечить рендеринг без доставки каких-либо пакетов на сторону клиента.
Пример использования серверных компонентов.
Мы проиллюстрируем, как можно использовать серверные компоненты как в традиционном React-приложении, так и в приложении Next.js.
Использование серверных компонентов в React-приложении.
В обычном React-приложении серверный компонент похож на обычный React-компонент.
Также обратите внимание, что для использования async/await в компоненте typescript с файлом .tsx вам необходимо обновить версию typescript до 5.1.1. Подробнее об этом можно прочитать здесь
Ниже приведен пример серверного компонента,
// Серверный компонент
const BlogPost = async ({ id, isEditing }) => {
const post = await db.posts.get(id);
return (
<div>
{' '}
<h1>{post.title}</h1> <section>{post.body}</section>{' '}
</div>
);
};
Клиентский компонент выглядит как обычный компонент React, но в файл компонента добавляется директива 'use client``. Директива’use client“ технически объявляет границу между серверным и клиентским компонентом.
// Клиентский компонент
'use client' import React, { useState } from "react"; import { v4 as uuidv4 } from 'uuid'; const PostEditor = ({ blogPost }) => { const [post, setPost] = useState<any>({ id: uuidv4(), title: blogPost.title, content: blogPost.content, }) const onChange = (type: any, value: any)=> { switch(type){ case "title": setPost({...post, title: value}) break; case "content": setPost({...post, content: value}) break; default: break } } const submitPost = ()=> {
// сохраняем запись в блоге
}; return ( <div> <div className="md:mx-auto px-6 md:px-0 mt-10 md:w-9/12"> <h1 className="my-4 text-center">Создать пост</h1> <form onSubmit={submitPost}> <div className="mt-8"> <label className="text-white mb-2"> Заголовок </label> <input type="text" placeholder="" value={post.title} required onChange={(e)=> onChange("title", e.target.value)} /> </div> <div className="mt-8"> <label className="text-white mb-2"> Добавить содержимое вашего блога </label> <textarea value={post.content} required onChange={(e)=> onChange("content", e.target.value)} ></textarea> </div> <div className="flex justify-end mt-8"> <button type="submit" className="px-4 py-4 bg-[#0e9f64] c-white border-radius" > Создать пост </button> </div> </form> </div> </div> ); }; export default PostEditor;
При работе с серверными и клиентскими компонентами необходимо знать определенные правила:
Серверные компоненты нельзя импортировать в клиентские компоненты, но клиентские компоненты можно импортировать в серверные компоненты. Мы проиллюстрируем, как импортировать клиентский компонент в серверный, на нашем предыдущем примере, показанном ниже:
// Серверный компонент
import db from 'db';
import NoteEditor from 'NoteEditor';
async function BlogPost({ id, isEditing }) {
const post = await db.posts.get(id);
return (
<div>
{' '}
<h1>{post.title}</h1> <section>{post.body}</section> {isEditing ? (
<PostEditor blogPost={post} />
) : null}{' '}
</div>
);
}
В приведенном выше коде мы импортировали PostEditor (клиентский компонент) в серверный компонент.
Серверный компонент может быть передан в качестве дочернего реквизита клиентскому компоненту, когда клиентский компонент находится внутри серверного компонента.
const ServerComponent1 = () => {
return (
<ClientComponent>
{' '}
<ServerComponent2 />{' '}
</ClientComponent>
);
};
Использование серверных компонентов в приложении Next.
Серверный компонент по умолчанию - это обычный React-компонент, созданный в новой директории App в Next 13.
// Серверный компонент
const BlogPost = async ({ id, isEditing }) => {
const post = await db.posts.get(id);
return (
<div>
{' '}
<h1>{post.title}</h1> <section>{post.body}</section>{' '}
</div>
);
};
Клиентский компонент в Next 13 выглядит как обычный React-компонент, но в файл компонента добавлена директива `‘use client“.
// Клиентский компонент
'use client' import React, { useState } from "react"; import { v4 as uuidv4 } from 'uuid'; const PostEditor = ({ blogPost }) => { const [post, setPost] = useState<any>({ id: uuidv4(), title: blogPost.title, content: blogPost.content, }) const onChange = (type: any, value: any)=> { switch(type){ case "title": setPost({...post, title: value}) break; case "content": setPost({...post, content: value}) break; default: break } } const submitPost = ()=> {
// сохраняем запись в блоге
}; return ( <div> <div className="md:mx-auto px-6 md:px-0 mt-10 md:w-9/12"> <h1 className="my-4 text-center">Создать пост</h1> <form onSubmit={submitPost}> <div className="mt-8"> <label className="text-white mb-2"> Заголовок </label> <input type="text" placeholder="" value={post.title} required onChange={(e)=> onChange("title", e.target.value)} /> </div> <div className="mt-8"> <label className="text-white mb-2"> Добавить содержимое вашего блога </label> <textarea value={post.content} required onChange={(e)=> onChange("content", e.target.value)} ></textarea> </div> <div className="flex justify-end mt-8"> <button type="submit" className="px-4 py-4 bg-[#0e9f64] c-white border-radius" > Создать пост </button> </div> </form> </div> </div> ); }; export default PostEditor;
Плюсы и минусы серверных компонентов React.
Мы рассмотрим преимущества включения серверных компонентов в разработку, а также недостатки, которые возникают при их использовании.
Плюсы:
Сокращение пакета: Серверные компоненты - это компоненты ”нулевого пакета”, поскольку они не увеличивают размер пакета Javascript, который будет отображаться на клиенте.
Доступ к серверной инфраструктуре: Серверные компоненты обеспечивают беспрепятственный доступ к серверной инфраструктуре, такой как базы данных, файловая система и многое другое. Уменьшение задержки на клиенте, поскольку можно делегировать вызовы API серверным компонентам, которые работают на сервере.
Минусы:
Серверные компоненты не могут получить доступ к функциям на стороне клиента. Его внедрение может быть не быстрым, так как серверные компоненты могут предоставлять почти те же преимущества, что и обычные приложения SSR (server-side rendered), а многие уже привыкли к SSR. Поскольку серверные компоненты имеют доступ к серверной инфраструктуре, это может привести к некачественному дизайну приложений, так как может побудить разработчиков отказаться от создания API или даже автономных бэкендов, чтобы выполнять запросы и соединения с базами данных через серверные компоненты напрямую.
Выводы
В этой статье мы рассмотрели серверные компоненты в React, обсудили их использование и преимущества. Серверные компоненты React позволяют нам по-новому сочетать в React-приложениях все лучшее, что есть в клиентских и серверных рендерных компонентах. Надеюсь, эта статья убедит вас опробовать серверные компоненты React уже сегодня.