Настя Котова // Frontend & Node.js
575 subscribers
39 photos
2 files
107 links
Фронтендерица с лапками 🐾
Посты каждый понедельник 💃 Копаюсь во внутрянке технологий и рассказываю вам
Download Telegram
🪄 Разбираемся в семантическом версионировании: как управлять зависимостями в npm без хаоса

Семантическое версионирование, или SemVer, является популярной системой версионирования в мире программирования, особенно в экосистеме Node.js и npm. Эта методология активно применяется для управления версиями пакетов и модулей, чтобы облегчить управление зависимостями и минимизировать риски, связанные с обновлениями. Разберём основные составляющие семантического версионирования:

1️⃣ Основной номер версии (Major version) – увеличивается, если сделаны изменения, ломающие обратную совместимость.
2️⃣ Минорный номер версии (Minor version) – увеличивается, когда добавляются новые возможности, не ломающие существующий функционал.
3️⃣ Патч-версия (Patch version) – увеличивается при внесении исправлений ошибок, которые не затрагивают интерфейсы и не добавляют новых функциональностей.

👉 Пример версии по SemVer: 2.3.1, где 2 – основной номер, 3 – минорный, 1 – патч.

В package.json версии зависимостей можно обозначать следующими способами:

🔹 Точная версия: Указание конкретной версии, например, "react": "18.2.0". Это гарантирует, что будет использована именно эта версия.

🔹 Звездочка (*) или x: Означает использование любой версии в пределах указанной мажорной версии. Например: "react": "18.x"

🔹 Знак тильда (~): Использование тильды, например, "eslint": "~7.2.0", позволяет установить последнюю патч-версию для указанного минорного изменения. Это означает, что будут использованы все последующие патчи к версии 7.2.

🔹 Знак каретки (^): Каретка, например, "webpack": "^4.0.0", позволяет обновляться до последней минорной версии в рамках мажорной версии 4. Так, npm сможет установить любую версию от 4.0.0 до 5.0.0 не включительно.

🔹 Оператор сравнения (>=): Определяет минимально допустимую версию. Например, если указано "lodash": ">=4.17.0" , npm будет устанавливать версию lodash не ниже 4.17.0. Это означает, что при наличии более новых версий, подходящих под эту спецификацию, они будут установлены. Но с этим способом стоит быть осторожнее, так как могут выйти новые мажорные версии, сильно ломающие обратную совместимость.

🔹 Git-репозитории и теги: Можно указать зависимость от конкретного git-commit'a или тега, например, "a-library": "github:myusername/a-library#v1.0.0".

Использование этих подходов позволяет контролировать версии зависимостей в проекте, минимизируя риски поломок из-за неожиданных изменений в используемых библиотеках и модулях. SemVer и npm предоставляют разработчикам гибкие инструменты для управления зависимостями в их проектах.

🔗 Подробнее о семанитическом версионировании можно прочитать в официальной документации. А в этой статье подробно описано использование SemVer в npm.
🔥2
🚀 React 19: разбираемся в обновлениях!

Не так давно вышла новая версия одного из самых распространенных фреймворков для разработки фронтенда. Сегодня разберемся в обновлениях и ключевых фичах этого релиза.

🔹 Новые Хуки

1. useTransition - позволяет управлять приоритетом рендеринга, делая приложение более отзывчивым и плавным.
2. useActionState - упрощает работу с состоянием, связанным с асинхронными действиями, такими как подтверждение данных или загрузка.
3. useOptimistic - позволяет интерфейсу мгновенно реагировать на действия пользователя, в то время как данные подтверждаются на сервере.
4. useFormStatus - предназначен для управления состоянием форм, такими как валидация, отслеживание изменений и управление отправкой.
5. useDeferredValue - позволяет отложить обновления некритичных данных, что может быть полезно для поддержания быстродействия интерфейса при обработке больших объемов данных или сложных рендерингов.

🔹 Новое API "use"

React 19 ввёл новое универсальное API "use", которое унифицирует чтение данных из различных ресурсов при рендеринге. Сейчас это API поддерживает получение данных из Promise и контекста, в дальнейшем планируется расширение его возможностей.

🔹 React Server Components

Server Components продолжают развиваться, позволяя разрабатывать компоненты, рендеринг которых происходит на сервере. Это ускоряет загрузку страниц и уменьшает ресурсоёмкость клиента. Server Actions дполнительно упрощают взаимодействие с сервером, интегрируя логику обработки действий непосредственно в серверные компоненты.

🔹 Улучшенная обработка ошибок

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

🔹 Дополнительные усовершенствования

- Новая реализация паттерна Provider для контекста, которая упрощает передачу данных через компоненты.
- Добавление функций очистки для ссылок, которая помогает управлять памятью и ресурсами эффективнее.

🔗 Читайте полный список изменений и подробности на официальном сайте React.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21
💥 Новый TypeScript 5.5: Основные функции и улучшения!

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

1️⃣ Улучшенная инференция типов

TypeScript 5.5 продолжает улучшать систему типов, обеспечивая более точное и умное выведение типов. Это значительно упрощает работу с сложными структурами данных без необходимости явного указания типов. Лично мне кажется, что это одно из самых долгожданных улучшений.

2️⃣ Контроль синтаксиса регулярных выражений

Теперь, если вы допустили ошибку при написании регулярного выражения, TypeScript это подсветит и выбросит ошибку. Кажется, что этого очень давно не хватало.

3️⃣ Переменная ${configDir} внутри tsconfig.json

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

4️⃣ Поддержка новых методов Set

TypeScript теперь поддерживает новые методы для работы с Set, такими как union, difference, intersection и другие.

5️⃣ Оптимизация производительности компилятора

Разработчики TypeScript уделили внимание улучшению производительности компилятора, что приводит к более быстрому компилированию кода и ускорению процесса разработки.

6️⃣ Улучшения в инструментах и IDE поддержке

TypeScript 5.5 включает улучшения в интеграции с IDE, такие как более интуитивные подсказки и автодополнения, что делает процесс разработки более комфортным и эффективным.

🔗 И это далеко не все изменения в релизе. Подробнее можно узнать в официальном блоге TypeScript.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21
Про попапы ходит невероятное количество споров, но иногда это действительно страшная вещь!
🤡2
🚀 Регулярные выражения в Typescript 5.5

Недавно Оля писала о последнем обновлении TypeScript 5.5, и меня особенно заинтересовал пункт «Контроль синтаксиса регулярных выражений».

Я вообще фанат регулярок, еще в университете мне хорошо объяснили их, и с тех пор я активно использую их в рабочих задачах. Поэтому я решила поподробнее посмотреть, что нового добавил здесь TypeScript.

Во-первых, проверка синтаксиса будет осуществляться только в пределах литералов регулярных выражений. Например, этот код проверяться будет:


let myRegex = /@robot(\s+(please|immediately))?/;


А вот этот уже нет:


let myRegex = new RegExp("@robot(\s+(please|immediately))?");


В новую версию добавлена базовая проверка на синтаксис регулярных выражений, вроде забытых открывающих или закрывающих скобок.

Кроме того, TypeScript теперь будет проверять наличие используемых скобочных групп (включая именованные) и доступность фичей регулярных выражений в используемой версии ECMAScript.

Посмотрим, насколько это поможет на практике. Лично я всё равно всегда пользуюсь специальными сервисами для тестирования регулярок, например, https://regex101.com/, а потом уже копирую их в код.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
📢 Новые хуки в React 19! Часть 1.

В одном из предыдущих постов мы писали про обновление React 19, и вы проголосовали за новые хуки для работы с состоянием! 💡

Я решила поразбираться в них, чтобы понять, что к чему и чем они отличаются от наших старых добрых и любимых useState и useReducer. На разборе у меня были три самых свежих и интересных:

1. useActionState
2. useFormStatus
3. useOptimistic

Все примеры лежат в нашем репозитории здесь 👉 GitHub

🔍 useActionState и useFormStatus

В документации React говорится, что useActionState даст преимущество в работе с Server Actions, поэтому я подняла проект, используя Next 15, который из коробки поддерживает React 19 (и сам находится в бете).

Без использования директивы "use server", useActionState может показаться ничем не отличающимся от useState. Однако в сочетании с Server Action он становится очень интересным инструментом.

В самом первом примере есть простая форма с одним полем, которая отправляет данные на сервер с помощью useActionState.


export function FormUseActionState() {
const [result, formAction] = useActionState(submitForm, null);

return (
<form action={formAction}>
<h2>Form With useActionState</h2>
<input name="title" />

<button type="submit">Save!</button>
</form>
);
}



'use server';

export const submitForm = (prevState: string | null, form: FormData) => {
return form.get('title')?.toString() || '';
};


Примечательно, что в случае работы с Server Action нам достаточно просто передать его первым аргументом в useActionState, и дальше мы можем использовать возвращаемый из хука formAction напрямую в теге form. Таким образом мы:
1. совершенно не думаем о том, каким образом данные будут ходить с клиента на сервер и обратно
2. полагаемся на нативную браузерную обработку форм, не используя дополнительные обработчики onChange каждого поля и отдельные состояния, если нам это не нужно
3. можем миксовать серверный и клиентский код рядом друг с другом

Во втором примере можно увидеть, что у нас также доступен isPending:


const [result, formAction, isPending] = useActionState(submitForm, null);


Однако если у нас произойдет какая-то ошибка в серверном submitForm, то все наше приложение упадет - можно посмотреть на 3 примере. А это значит, что мы обязательно должны позаботиться об обработке ошибок в той функции, которую передаём хуку useActionState.

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


const { pending } = useFormStatus();


✍️ Во второй части разберем хук useOptimistic, а пока пишите в комментариях, что показалось интересным и как, по вашему мнению, часто будут использоваться новые хуки!

❤️ Реагируйте, если хотите увидеть обзор на Server Components и Server Actions!
👍21
🎉 Недавно мои коллеги из Яндекса публично зарелизили классный продукт — CodeRun. Это как LeetCode, но с уникальными задачами на русском, а еще с более новыми версиями компиляторов.

А для фронтендеров могут быть интересны задачки на вёрстку картинок на чистом HTML и CSS! Вот ссылка: Задачки на вёрстку

Это не реклама (честно-честно), просто я думаю, ребята сделали действительно полезный продукт, которым можно пользоваться в том числе и для подготовки к собеседованиям.

👍 Ставьте реакции, если хотите от нас разбор каких-нибудь интересных задач на алгоритмы!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍2
📢 Новые хуки в React 19! Часть 2

В предыдущей части мы посмотрели на хуки useActionState и useFormStatus, а сегодня разберем useOptimistic💡

Напоминаю, что все примеры лежат в нашем репозитории здесь 👉 GitHub

🔍 useOptimistic

Часто в современных интерфейсах используют подход, который называется Optimistic UI. Он заключается в том, что мы предполагаем, что на бэкенде запрос выполнится успешно, поэтому сразу можем показать пользователям обновленное состояние.

Ранее для реализации такого поведения приходилось сильно хитрить. Нельзя просто вызвать useState в обработчике перед асинхронной операцией и ожидать, что React сразу же отрисует новое состояние. Посмотрите на пример 5:


const [optimisticResult, setOptimisticResult] = useState(store);

... = useActionState(async (__: string | null, formData: FormData) => {
setOptimisticResult((prevState) => ({
title: formData.get('title')?.toString() || prevState.title,
description: formData.get('description')?.toString() || prevState.description,
}));

await saveData(formData);

return 'ok';
}, null);


Дело в том, что в React есть специальный механизм оптимизации — Batching, который не позволяет изменениям произойти до получения ответа от сервера.

Эту проблему решает новый хук useOptimistic. Если заменить в примере useState на этот хук, всё заработает правильно. Пример 6 использования нового хука:


const [optimisticResult, setOptimisticResult] = useOptimistic(store);

... = useActionState(async (__: string | null, formData: FormData) => {
setOptimisticResult((prevState) => ({
title: formData.get('title')?.toString() || prevState.title,
description: formData.get('description')?.toString() || prevState.description,
}));

await saveData(formData);

return 'ok';
}, null);


А если наша отправка формы завершится ошибкой, “оптимистичное” состояние автоматически сбросится. Пример использования с обработкой ошибок:


const [optimisticResult, setOptimisticResult] = useOptimistic(store);

... = useActionState(async (__: string | null, formData: FormData) => {
setOptimisticResult((prevState) => ({
title: formData.get('title')?.toString() || prevState.title,
description: formData.get('description')?.toString() || prevState.description,
}));

try {
await saveData(formData);
} catch (e) {
return 'not ok';
}

return 'ok';
}, null);


Дело в том, что при использовании useOptimistic мы передаем в него то значение, для которого хотим сделать оптимистичную версию (в данном примере это store).


const [optimisticResult, setOptimisticResult] = useOptimistic(store);


После выполнения обработчика в хук запишется значение, соответствующее текущему состоянию store.


✍️ Пишите в комментариях, полезней ли на ваш взгляд хук useOptimistic, чем два предыдущих. Мне кажется, этот хук будет использоваться чаще, чем useActionState и useFormStatus.
👍2
📝 Объект console и его методы

Недавно у меня возникла необходимость красиво вывести результат выполнения одного скрипта в консоль, в идеале в виде таблички. И тут я вспомнила, что у объекта console в JavaScript существуют не только методы log и error, которые чаще всего используются, но ещё и ряд других, и поэтому я решила немного в них покопаться.

📌 Первые, и самые простые, это log, info, warn, error и debug. Первый просто выводит сообщения любого вида в консоль, а остальные в целом делают то же самое, но задают сообщению так называемый log level. Например, warn покрасит сообщение в жёлтый, а error в красный, но итоговое оформление будет зависеть от среды выполнения. А метод debug выведет сообщение только в том случае, если в консоли включен режим дебага.

📌 Следующий метод как раз помог мне решить мою первоначальную задачу. Метод table принимает либо двумерный массив, задающий строки таблицы, либо массив объектов с одинаковыми ключами, и на основе этих данных выводит данные в виде таблицы. Её вид также может отличаться в зависимости от среды выполнения.

📌 Совсем неожиданными для меня стали методы group и groupEnd , которые позволяют группировать выводимые в консоль сообщения, при этом групп может быть несколько и они могут быть вложенными. Например (выравнивание сделано для лучшей читаемости):


console.group("Группа");
console.log("Внутри группы");
console.group("Подгруппа");
console.log("Внутри подгруппы");
console.groupEnd();
console.groupEnd();


📌 Ещё интересные методы - это assert и count , которые могут быть полезны при дебаге. Первый позволяет проверить истинность выражения, а второй может посчитать количество вызовов сообщений с определённой меткой. Метод count может также быть полезен, если нужно посчитать количество ререндеров конкретного компонента. Примеры:


console.assert(1 === 2, "Неправильно!"); // Выведет "Неправильно!", если утверждение ложное

console.count("Метка"); // Выведет "Метка: 1"
console.count("Метка"); // Выведет "Метка: 2"


📌 Ну и последнее, но не менее важное, что хотелось бы рассмотреть - это методы time и timeEnd, которые берут на себя измерение времени между двумя точками в выполнении кода. Например:


console.time("Таймер"); // Задаём таймеру метку
for(let i = 0; i < 1000000; i++) {} // Некий код, который занимает время
console.timeEnd("Таймер"); // Выведет время, затраченное на выполнение цикла


🔗 И это были не все методы, которые есть в объекте console. Подробнее об этих и других методах можно почитать на MDN, а поэкспериментировать с ними можно прямо сейчас в консоли вашего бразуера!
👍2
👆 Примеры вывода методов group и table
👍2
🤝🏻 Двусторонняя клиент-серверная связь. Часть 1: Long Polling

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

Самый простой вариант реализации такого поведение - делать периодические запросы к серверу за новыми данными. Но, во-первых, это порождает кучу лишних запросов и излишнюю нагрузку на сервер, и, во-вторых, данные будут приходить с очевидной задержкой.

💡 Один из более оптимальных вариантов реализации - это использование Long Polling. Фактически, это поддержание соединения между клиентом и сервером посредством обыкновенного длинного http-запроса, который удерживается сервером, пока в нём не появятся новые данные или не пройдёт сетевой таймаут. При этом после успешного ответа, таймаута или ошибки клиент инициирует новый запрос и цикл повторяется.

Простой пример реализации API с использованием библиотеки Express:


const messagesQueue = [];

// Route для отправки сообщений на сервер
app.post('/send', express.json(), (req, res) => {
const { message } = req.body;
messagesQueue.push(message);
res.status(200).send('Message received');
});

// Route для long polling
app.get('/poll', (req, res) => {
function checkForMessages() {
if (messagesQueue.length > 0) {
const messages = messagesQueue;
messagesQueue = [];
res.json(messages);
} else {
// Проверяем сообщения каждые 500 мс
setTimeout(checkForMessages, 500);
}
}
checkForMessages();
});


Как результат, на клиентах обновления будут ловиться с помощью метода /poll, а обновляться с помощью метода /send. Один из плюсов этого метода с точки зрения сервера в том, что для его поддержания не нужны какие-либо дополнительные настройки, так как используются обыкновенные http-запросы.

🤔 Звучит просто и круто, а какие у этого всего есть недостатки?

1. Использование ресурсов сервера - каждый Long Polling запрос требует, чтобы сервер удерживал соединение в открытом состоянии, что потребляет ресурсы сервера. Это может стать проблемой при большом количестве клиентов.

2. Задержка ответа - существует задержка между моментом появления данных и их получением клиентом, поскольку сервер должен дождаться окончания заданного интервала или появления данных перед тем, как ответить на заявку.

3. Управление ошибками и таймаутами - требуется аккуратное управление ошибками и таймаутами, чтобы избежать утечки ресурсов и удерживания соединений открытыми без необходимости.

4. Масштабируемость - масштабирование приложений с использованием Long Polling может быть сложным, поскольку каждое удерживаемое соединение занимает серверные ресурсы.

5. Отсутствие симметричности отправки и приёма сообщений - Long Polling оптимизирован для сценариев, где клиент чаще получает данные, чем отправляет. Направленная связь в обратном направлении не так эффективна.

🪄 Чуть более сложный пример реализации для обыкновенного чата на основе Long Polling есть в нашем репозитории. А в Live Demo можно потрогать результат и пообщаться!

⚡️ Как можно заметить, у этого метода есть много недостатков. Ставьте реакции, если вам интересна эта тема, чтобы углубиться в более эффективные способы реализации двусторонней связи между клиентом и сервером, такие как WebSockets, Server-Sent Events и WebRTC.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2
📌 Навигация по каналу

Если вы только присоединились к нам или просто хотите освежить знания, мы составили для вас удобную подборку наших материалов по ключевым темам:

👨‍💻 JavaScript:
- Потеря контекста, this и globalThis:
- Часть 1
- Часть 2
- Объект console и его методы
- IntersectionObserver
- CommonJS и ECMAScript Modules: в чём разница?

🖌️ CSS:
- Анимации в CSS:
- Часть 1
- Часть 2
- Z-index и контекст наложения:
- Часть 1
- Часть 2
- Видео на канале
- Как сделать адаптивный iframe
- Как подключить локальные шрифты к вашему сайту

📜 HTML теги:
- picture
- audio и video
- Теги для форм
- svg

💻 TypeScript:
- Полезные директивы TypeScript
- TypeScript 5.5:
- Часть 1
- Часть 2

🔥 React:
- Обзор новинок React 19
- Новые хуки React 19 с примерами:
- Часть 1
- Часть 2
- React Compiler
- Server Components и Server Actions:
- Часть 1
- Часть 2
- State-менеджеры vs React Hooks: когда и что лучше использовать
- Библиотека react-use
- useLayoutEffect

🤝 Двусторонняя клиент-серверная связь:
- Long Polling
- WebSocket
- Server-Sent Events

🌐 Сети и инфраструктура:
- Что такое CDN
- Что такое S3
- JWT токены

🌟 Веб-разработка:
- Как работают поисковые системы и как поднять свой сайт в поисковой выдаче
- Что нужно знать фронтенд-разработчику за пределами фронтенда
- Полезные фишки DevTools
- UI-библиотеки

🛠️ Инструменты сборки:
- Виды зависимостей в package.json
- Семантическое версионирование
- Линтеры на фронтенде: Prettier, ESLint, Stylelint
- Prettier, ESLint и Stylelint: в чем разница?
- Как настроить Prettier, ESlint, Styleling и Husky в проекте с нуля
- Пресет для демо с помощью Vite и Express
- Инструменты для сборки на фронтенде

🔒 Безопасность:
- Что такое CSRF

💡 Архитектура:
- Отличия Stateless и Statefull

📖 Приятного чтения!

Лайки, комментарии и репосты помогают нам развиваться и делать контент ещё лучше! 👍
👍3
Настя Котова // Frontend & Node.js pinned «📌 Навигация по каналу Если вы только присоединились к нам или просто хотите освежить знания, мы составили для вас удобную подборку наших материалов по ключевым темам: 👨‍💻 JavaScript: - Потеря контекста, this и globalThis: - Часть 1 - Часть 2 - Объект…»
🚀 React Compiler

Сегодня разберем новый экспериментальный компилятор от команды React — React Compiler. Этот компилятор призван улучшить производительность приложений React, оптимизируя их на этапе сборки. Пока он еще в стадии разработки, но уже доступен для тестирования.

Особенности
- Работает с обычным JavaScript и следует правилам React, что позволяет использовать его без переписывания кода.
- Включает в себя плагин eslint, который помогает улучшить качество кода, показывая анализ компилятора прямо в вашей IDE.
- Поддерживает React 19 RC.

Как это работает
React Compiler помогает ре-рендерить компоненты только там, где это действительно необходимо. Например:

import { useState } from 'react';

function Button({ title }) {
console.log('[Button] Render');
return <button type="button">{title}</button>;
}

function Form() {
const [name, setName] = useState('');
const handleChangeName = (event) => setName(event.target.value);

console.log('[Form] Render');

return (
<form>
<input name="firstName" value={name} onChange={handleChangeName} />
<Button title="Ok" />
</form>
);
}


В стандартной ситуации, каждое изменение в input приводит к перерендеру кнопки в форме. Чтобы избежать этого раньше нам пришлось бы обернуть этот компонент в React.iss.onemo(). Однако, с React Compiler компонент Button будет перерисовываться только когда это действительно требуется, благодаря автоматической мемоизации.

🔗 Полезные ссылки:
- Плагин для eslint
- Подключение к проекту с помощью babel-плагина
- Пример использования в проекте на vite можно найти в нашем репозитории на GitHub

Это звучит как магия, и я, если честно, не до конца верила, что такое может работать, но оно действительно работает!)
👍1
🤝🏻 Двусторонняя клиент-серверная связь, часть 2: WebSocket

Несколько постов назад я писала о том, что порой нам нужно поддерживать двустороннюю связь между клиентом и сервером и приводила пример реализации такой связи с помощью Long Polling. Сегодня поговорим о другом более распространенном методе реализации такой связи - использование WebSocket.

Итак, с помощью WebSocket можно открыть так называемый “туннель” между клиентом и сервером, который будет пересылать данные в обоих направлениях. При этом сервер должен предоставлять адрес, по которому этот туннель можно открыть, и обрабатывать входящие соединения, а клиент, в свою очередь, будет инициировать эти соединения.

Передавать в такой туннель можно не любые, а только текстовые и двоичные данные (binary data), а также специальные фреймы, которые помогают отслеживать и менять статус соединения. То есть перекидываться совсем любыми JavaScript-объектами не получится, но вряд ли это будет проблемой. В качестве примера можно рассмотреть небольшой фрагмент кода на Express, который принимает объекты сообщений, которые приведены к строке через JSON.stringify():


const express = require('express');
const expressWs = require('express-ws');

const app = express();
const wsApp = expressWs(app);

const allMessages = [];

// Открываем урл для соединений
app.ws('/chat', (ws, _) => {

// Подписываемся на сообщения от клиентов
ws.on('message', (message) => {

// Сохраняем сообщение в виде исходного объекта
allMessages.push(JSON.parse(message));

// Рассылаем сообщение всем остальным подключенным клиентам
wsApp.getWss().clients.forEach((client) => {
client.send(message.toString());
});
});
});


Если наш сервер поднят на https://localhost:8000, то туннель будет доступен по адресу ws://localhost:8000/chat. При этом подключение на клиенте будет выглядеть следующим образом:


const ws = new WebSocket('ws://localhost:8000/chat');

// Подписываемся на сообщения от сервера
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
};

// Отправляем новое сообщение на сервер
const sendMessage = (message) => {
ws.send(JSON.stringify(message));
}


Вот и всё! На самом деле, базово всё очень просто. Конечно, WebSocket API предоставляет больше методов, чем onmessage и send, но про них можно почитать на MDN.

🔗 Более подробный пример реализации для чата есть в нашем репозитории. И для этого типа связи мы также подготовили Live Demo, в котором можно потрогать результат и пообщаться.

🤔 И снова вопрос: какие тут могут быть сложности? А вот какие:

1. Безопасность: По умолчанию WebSocket не обеспечивает шифрование данных, что может создать уязвимости. Чтобы обеспечить безопасность, следует использовать WSS (WebSocket Secure), который работает через TLS/SSL.

2. Совместимость с браузерами: Хотя поддержка WebSockets хороша в современных браузерах, в старых версиях или некоторых браузерах поддержка может отсутствовать или быть неполной.

3. Масштабируемость: Управление большим количеством соединений WebSocket также может быть ресурсоемким, и требует дополнительных настроек на сервере для поддержания большого числа одновременных соединений.

4. Управление состоянием: В отличие от стандартных HTTP-запросов, WebSocket поддерживает постоянное соединение, что означает дополнительную сложность в управлении состоянием соединения.

5. Обработка ошибок и повторные соединения: Необходимо предусмотреть механизмы для обработки потери соединения и автоматического повторного соединения.

 Но если отойти от нюансов, то WebSockets - это достаточно мощный интрумент, которым пользуется огромное количество сервисов с real-time данными!
👍2
Server Components и Server Actions. Часть 1.

Саму концепцию серверных компонентов нам представили еще несколько лет назад. В том числе, она активно используется в 14 версии Next.js в App Router, который создатели фреймворка теперь предлагают по умолчанию для разработки новых приложений. Однако в React серверные компоненты и экшены были анонсированы только в 19 версии. Я решила посмотреть поподробнее, что это за покемоны.

Server Component - это неинтерактивный компонент, который работает исключительно на стороне сервера.

Server Action - это функция, которая работает на сервере, но может использоваться клиентским компонентом, например как пропс-промис, который передается в этот компонент.

Давайте посмотрим на ещё несколько интересных примеров работы с ними поподробнее. Для них будем использовать все тот же Next.js 15, который поддерживает React 19.

👀 По умолчанию в App Router все страницы и компоненты внутри них являются серверными, до тех пор, пока мы не указали обратное.

В чем особенности серверного компонента в сравнении с клиентским

🔹 Серверный компонент может быть асинхронным.

Пример простого серверного компонента, который получает данные из API другого сервиса и сразу же отображает их:

export async function List() {
const data = (await fetch('https://dummyjson.com/products/search?limit=10').then((res) => res.json()));
console.log('[List] Server Component Render');

return (
<ul>
{data.products.map((product) => (
<li key={product.id}>
{product.id}: <b>{product.title}</b>
</li>
))}
</ul>
);
}


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

🔹 Серверный компонент рендерится только один раз на сервере.

Для компонента выше у нас будет всего лишь один вывод в консоль '[List] Server Component Render' - на сервере. При классическом Server Side Rendering таких выводов будет два - один на сервере, во время формирования HTML, и один на клиенте - во время гидратации. Для серверных же компонентов гидратация не нужна.

🔹 Серверный компонент не может рендерится напрямую в клиентском компоненте, но его можно передавать туда как props.

Рассмотрим пример, у нас есть компонент Switcher, который в зависимости от состояния отрисовывает один или другой компонент:

type ComponentType = 'list' | 'searchedList';

export function Switcher({ ListComponent, SearchedListComponent }: Props) {
const [type, setType] = useState<ComponentType>('list');

const handleClick = (newType: ComponentType) => () => setType(newType);

return (
<section>
<div>
<button onClick={handleClick('list')}>List</button>
<button onClick={handleClick('searchedList')}>Searched List</button>
</div>
{type === 'list' ? ListComponent : SearchedListComponent}
</section>
);
}


Дальше как пропсы в этот компонент мы можем передать как клиентский компонент, так и серверный:

<Switcher
// серверный компонент
ListComponent={<List />}
// клиентский компонент
SearchedListComponent={<SearchedList />}
/>


🔹 При динамической подгрузке серверные компоненты передаются на клиент не в виде HTML, а в специальном формате

Вот как примерно выглядят пришедшие в браузер данные для новой страницы /about в нашем приложении на Next.js:

5:I["(app-pages-browser)/./node_modules/next/dist/client/link.js",...
6:I["(app-pages-browser)/...
7:I["(app-pages-browser)/...
2:{"name":"","env":"Server","owner":null}
1:D"$2"
4:{"name":"About","env":"Server","owner":null}
3:D"$4"
3:["$","div",null,{"className":"page_page__bHvK0","children":...
...


Из этой структуры React на клиенте сможет построить DOM-дерево, но при этом не будет производить процесса гидратации, так как серверные компоненты не обладают динамикой.

📂 Подробнее примеры можно посмотреть в нашем репозитории на GitHub.

А в следующей части поговорим про Server Actions.
👍1
Server Components и Server Actions. Часть 2.

В предыдущем посте мы поговорили про Server Components, а сегодня рассмотрим Server Actions.

Ранее, когда мы рассказывали про новые хуки React, мы уже посмотрели на примерах, как можно работать с серверными экшенами в сочетании с хуком useActionForm (вот здесь можно почитать подробнее).

Но на самом деле нам не обязательно использовать новые хуки React или даже серверные компоненты для работы с серверными экшенами.

Посмотрим на пример простого компонента со списком товаров. Он также осуществляет поиск товаров по строке через внешнее API, в которое передается поисковый запрос:

'use client';
import { useState } from 'react';
import { search } from './searchAction';

export function SearchedList({ fetchInitialData }: Props) {
const [searchText, setSearchText] = useState('');
const [data, setData] = useState<SearchResponce | null>(null);

const handleChangeSearch = (event) => setSearchText(event.target.value);

const handleClick = async () => {
const result = await search(searchText);
setData(result);
};

return (
<section>

<div>
<input name="search" onChange={handleChangeSearch} value={searchText} />
<button type="submit" onClick={handleClick}>Search</button>
</div>
{data && (
<ul>
{data.products.map((product) => (
...
))}
</ul>
)}
</section>
);
}


Здесь search выглядит как обычная функция, но на самом деле это серверный экшен. Делает её таким директива 'use server'.

'use server';

export const search = async (query: string) => {
console.log('[SearchedList] Server Action');
const data = (await fetch(`https://dummyjson.com/products/search?limit=10&q=${query}`).then((res) => res.json()));
return data;
};


В чем отличия серверного экшена

🔹 Серверный экшен выполняется только на сервере.

В нашем примере вывод в консоль '[SearchedList] Server Action' будет выполнен на сервере. Если бы это была обычная функция, то этот вывод был бы в консоли браузера.

🔹 Общение между клиентом и сервером для серверных экшенов происходит посредством специальных API-зпросов.

При клике на кнопку Search в браузере будет происходить POST-запрос на тот же URL, на котором мы находимся (локально это https://localhost:3000/).
В ответ придут данные в специальном формате:

0:["$@1",["development",null]]
1:{"products":[{"id":101,"title":"Apple AirPods Max Silver","description":"The...


Таким образом нам не нужно добавлять новый эндпоинт в API, в коде мы получаем эти данные, как если бы мы просто вызвали какую-то функцию. При этом внутри функции search вместо запроса к внешнему API может быть запрос на чтение из файла или напрямую из базы данных.

Кроме этого мы можем передать promise из серверного экшена как пропс в наш клиентский компонент:

import { search } from './searchAction';

export function SearchedList() {
const fetchInitialData = search('');

return (
<Suspense fallback="Loading...">
<SearchedListBase fetchInitialData={fetchInitialData} />
</Suspense>
);
}


И дальше использовать новый метод use для того, чтобы дождаться результат его выполнения и применить как initialValue в хуке useState:

type Props = {
fetchInitialData: Promise<SearchResponce>;
};

export function SearchedList({ fetchInitialData }: Props) {
const initialData = use(fetchInitialData);
const [data, setData] = useState<SearchResponce>(initialData);
...
}


📂 Подробнее примеры можно посмотреть в нашем репозитории на GitHub.

Я считаю, что появление Server Components и Server Actions приближает ту эпоху, когда фронтендеру нужно уметь работать не только в среде браузера, но и быть немного бэкендером. Новые инструменты, с одной стороны, предоставляют много новых возможностей, в том числе и для оптимизации. Но как и со всеми новыми технологиями, нужно знать, как их готовить.

✍️ А как cчитаете вы - поделитесь в комментариях, что думаете о развитии React в сторону серверных компонетов и экшенов!