Telegram Web
Как менять состояние вкладки по интервалу

Полезная фича для демонстрации, например, уведомлений, которая может пригодиться на очень большом количестве сайтов

Для реализации нам необходимо создать состояния, между которыми мы хотим перемещаться:
const states = [
{ title: "Мессенджер", icon: Favicon1 },
{ title: "Новое сообщение", icon: Favicon2 },
]


Далее необходимо завести состояние, которое будет определять на каком этапе из списка состояний мы сейчас находимся. Это состояние можно будет менять через обычный интервал:
const [stateIndex, setStateIndex] = useState(0);

// раз в секунду переходим на следующее состояние
useEffect(() => {
const intervalId = setInterval(() => {
setStateIndex((prevIndex) => (prevIndex + 1) % states.length);
}, 1000);

return () => clearInterval(intervalId);
}, []);


И в зависимости от текущего состояния изменяем параметры вкладки:
useEffect(() => {
const link = document.querySelector("link[rel~='icon']");
const title = document.querySelector("head title");

if (link) {
link.href = states[stateIndex].icon;
title.textContent = states[stateIndex].title;
}
}, [stateIndex]);


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

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#code #web #theory #javascript #react
🐳22😐8👍54🔥2
Как определить, активна ли вкладка у пользователя?

Если вам нужно узнать, активна ли вкладка в браузере у пользователя, существует несколько простых методов для этого

Во-первых, у объекта document есть свойство hidden, которое указывает открыта ли вкладка на экране пользователя в конкретный момент времени:
// если true, значит вкладка работает в фоне
document.hidden // true

// вкладка открыта на весь экран
document.hidden // false


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

Чаще всего используется второй способ, а именно отслеживание события visibilitychange:
document.addEventListener("visibilitychange", function() {
if (document.hidden) {
console.log("Вкладка неактивна")
} else {
console.log("Вкладка активна")
}
})


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

Зачем это можно использовать? Да абсолютное множество применений:
— дополнительно сохранять данные на закрытие вкладки
— выключать аудио/видео при скрытии вкладки
— сбор аналитики
— и куча всего ещё

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#code #web #theory #javascript
👍23🔥5🐳32
"Тяжёлые" анимации

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

И для того, чтобы разобраться в нюансах, стоит помнить, что отрисовка контента страницы происходит в несколько этапов, таких как получение ресурсов, парсинг HTML и CSS, создание Render Tree и так далее. Более подробно можно прочитать в отличной статье на доке

Мы же сконцентрируемся на Layout и Paint фазах, их я кратко напомню:
- Layout — фаза отрисовки, в которой браузер рекурсивно проходится по заранее полученному Render Tree и рассчитывает позиции и размеры элементов относительно друг друга для их корректного расположения внутри вьюпорта

- Paint — по полученным позициям и размерам браузер рисует конкретные пиксели конкретных цветов


Также помним, что Paint без Layout не бывает, а если вызывается пересчёт Layout, то и в фазу Paint мы обязательно попадём, причём с нуля

Так вот, к чему это я: анимация любого свойства, которое так или иначе влияет на расположение элементов на странице будет вызывать Layout и Paint на каждый фрейм анимации, а сам по себе процесс отрисовки — это далеко не дёшево

Часто можно столкнуться с анимацией, когда какой-то элемент на странице (изображение, текст или ещё что-то) плавают по странице в зависимости от положения курсора. Там анимация беспрерывна, и если реализовать подобную анимацию через свойства top и left, например, то фазы Layout и Paint будут крутиться каждый раз, когда вы двигаете курсором, и из-за долгих рассчётов вся анимация будет дёрганной

Решается это просто — на помощь приходит Compositing

Compositing — это способ вынести все вычисления для рендера в отдельное ядро

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


Браузеры умеют применять это и самостоятельно, но у нас и у самих есть возможность вынести элемент на отдельный слой: использовать transform

Анимацию плавающего элемента нужно переделать с top и left на transform, и вот почему:

top и left триггерят Layout и Paint для всего потока, а может и всей страницы. transform выносит элемент в отдельный поток, что уменьшает объём вычислений и делает страницу более отзывчивой

Таким образом, к тяжёлым свойствам можно отнести все, что триггерят Reflow (повторный вызов Layout фазы) и Repaint:

Reflow триггерят анимации: width, height, top, left, font-size, border-width, margin, padding и так далее — все свойства, что могут влиять на расстановку элементов на странице

Repaint триггерят анимации: color, background-color, outline, visibility — все свойства, что влияют на отображение, но не на позицию элемента


Обычно для анимаций рекомендуют использовать transform и opacity. Это не значит, что нельзя использовать других свойств, но эти — самые оптимизированные для браузера

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #theory
👍3716🐳5🔥3
Немного о __proto__ и prototype в JavaScript

__proto__ — это внутреннее свойство любого объекта или примитива в JavaScript, которое ссылается на объект, от которого он наследует свойства и методы

prototype — это свойство функций-конструкторов (или классов), которое используется для определения объектов, которые будут выступать в роли прототипов для всех экземпляров, созданных этим конструктором

В чём разница?


__proto__ есть у каждого объекта и ссылается на прототип, из которого этот объект черпает свои свойства и методы

prototype есть только у функций-конструкторов (и классов) и используется для задания свойств и методов, которые будут доступны всем экземплярам, созданным с помощью этого конструктора

Если рассмотреть в коде, то получим следующее:
const name = "Denis"
const surname = "Putnov"

name.__proto__ === String.prototype // true
name.prototype // undefined
name.__proto__ === surname.__proto__ // true

const age = 23

age.__proto__ === Number.prototype // true
age.prototype // undefined


То есть, по факту, свойство __proto__ можно назвать некоторым костылём языка, благодаря которому мы можем понять с помощью чего конкретно был создан наш новый объект

__proto__ всегда ссылается на какой-то прототип, на основе которого был создан новый объект

В примере выше, мы при помощи __proto__ можем увидеть, что name в итоге создан при помощи прототипа String

Зачем всё это нужно? Для корректной работы прототипного наследования, конечно же. Рассмотрим ещё один пример кода:
const channel = {
name: "progway"
}

channel.toString()
// как же javascript'у понять, откуда взять метод?
// под капотом вызывается это вот так:

(channel.__proto__).toString()

// а мы знаем, что channel.__proto__ === Object.ptototype
// поэтому выглядеть вызов будет примерно вот так

Object.prototype.toString.call(channel)


Вообще, вся эта теория скорее всего не нужна рядовому разработчику. Это что-то из разряда теории ядра JavaScript, которая вроде есть, но не понятно зачем она нужна в таком приложении в реальной разработке

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


Если моё объяснение не очень понятно, то есть отличный видос у камасутры на эту тему, где всё разжёвано максимально понятно, советую

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #javascript #theory #code
👍35🐳4🔥31
Как я использую шину событий

Шина событий — это паттерн, который используется для взаимодействия различных компонентов системы не напрямую, а через некоторый посредник — саму шину

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


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

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

Также с помощью шины можно существенно улучшать перфоманс приложения, более подробно об этом сказано в отличном видео синяка


Главным минусамом я бы назвал некоторую непрозрачность: порой бывает сложно отследить куда и как протекают данные, особенно в больших приложениях, но ИМХО это относительно легко решается базовой организацией кода

Лично мне уже не раз доводилось использовать шину в проде. Нравится она мне своей простотой и тем, что позволяет легко соединять несоединяемое. В связке с реактом, можно легко избавиться от лишних ререндеров или, например, от prop-drilling'a

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

Я создаю отдельный файл для инициализации канала
export const {useEvent: useSpecificEvent, ...specificEventChannel} = createEventChannel<{
event: (options: Foo) => void;
}>()


И далее использую экспортируемые сущности примерно так:
// где угодно: публикация события в шину
specificEventChannel.emit('event', options)

// в react-компоненте: реакция на событие
useSpecificEvent('event', (options) => {
...
})


Конечно же, реагировать на событие в шине можно не только в компонентах: можно вообще где угодно. Для этого в specificEventChannel есть дополнительные функции on, off и once для подписки, отписки и единоразовой реакции соответственно

Вся "магия" тут заложена внутри функции createEventChannel. Сама по себе шина там достаточно типовая, что-то невероятное придумать сложно, однако код всё равно достаточно занятный, полный код можно найти в этом гисте

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #javascript #theory #code
23🐳7👍6🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Pattern matching

Pattern matching — это крутой концепт, который позволяет делать что либо в зависимости от совпадения с тем или иным шаблоном

В качестве шаблона может выступать какая-то константа или предикат


Самой примитивной реализацией концепта в JavaScript можно считать объекты или конструкцию switch-case:

const statusIcon = {
warning: <WarningIcon />,
success: <SuccessIcon />,
error: <ErrorIcon />,
loading: <Spinner />
}

const status = "error"
const matched = statusIcon[status]


Примерно то же самое можно реализовать и со switch-case, но всё это не так интересно

Есть такая библиотечка — ts-pattern. Она же позволяет отойти от примитивных примеров и использовать полноценный pattern matching, включая анализ вложенных структур и более сложные условия

На главной странице библиотечки есть очень наглядная гифка. Я предлагаю посмотреть на неё повнимательнее, описывать что-то дополнительно не вижу смысла

Скажу лишь то, что мне доводилось пользоваться этой библиотечкой и, как по мне, работает она шикарно. У меня остался исключительно положительный опыт)

Сам по себе концепт невероятно полезен для упрощения кода. Такой код проще читать, изменять и поддерживать. А в совокупности с ts-pattern, решение будет ещё и типобезопасно, что также неоспоримый плюс

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #javascript #theory #code
🔥24👍73🐳3🤔1
Порталы в React

Сталкивались когда-нибудь с проблемой, когда нужно рендерить элемент за пределами текущей DOM-иерархии?

Например, модальные окна, которые не должны быть вложены в основное дерево из-за проблем с позиционированием или всплывающие подсказки, которые всегда должны быть на переднем плане


Для таких задач React предлагает решение — порталы

В документации реакта приведён такой код:
import { createPortal } from 'react-dom';

<div>
<p>Текст расположен в родительском диве</p>
{createPortal(
<p>Текст расположен в document.body </p>,
document.body
)}
</div>


Работает этот код проще простого: элемент, переданный в функцию createPortal, будет маунтиться реактом не в родительский див, а в document.body. Работать это будет с деревом любой вложенности

Такой код будет работать, но он не очень удобен, поэтому многие компонентные библиотеки максимально упрощают порталы для разработчиков и делают подобные компоненты — они есть в Chakra UI, Material UI, Semantic UI и других либах

Но, на самом деле, там нет ничего сложного


Если максимально упростить, то можно прийти к такому варианту:
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>

Тут стоит уточнить две детали:

1. Мы создаём новый div внутри useState, чтобы проще было контролировать портал
Если мы будем рендерить контент сразу в document.body, то можно словить много проблем со стилями и отслеживанием самого портала

Используем мы именно useState, чтобы создавать элемент единожды и гарантировано на первый рендер компонента. Элемент создается внутри колбека инициализации состояния — он всегда вызывается единожды на маунт компонента

Как альтернатива, можно обойтись и рефом


2. В useLayoutEffect мы привязываем жизненный цикл тега-обёртки к циклу компонента портала
Тоже полезно, чтобы лишний раз не задумываться о том, как живёт портал и не создавать ненужных элементов в вёрстке

useLayoutEffect используется вместо useEffect, чтобы обрабатывать портал без лишних мерцаний и более плавно

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


Да и всё. Тут компонент на 10 строчек буквально, ничего сверхъестественного. Если вам нужны порталы, то задумайтесь — скорее всего вам хватит такой простой реализации

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #javascript #theory #code
21👍11🐳3🔥1👏1
Составные компоненты

Есть такой паттерн для реакта, который называется Compound Components. Это можно перевести как "составные компоненты"

Смысл этого паттерна заключается в том, что мы можем связать компоненты общим окружением и эффективнее использовать их вместе

То есть мы можем заранее объединить компоненты каким-то контекстом и переиспользовать их, например, через общее пространство имён, в качестве которого чаще всего выступает родительский компонент


Запутались в словах? Лучше посмотреть в коде:

Вот пример прям из доки Ant Design:
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

Зачем же так сделали? Тут преследуется три цели:
1. Явно показать на уровне нейминга, что использовать Layout.Footer вне Layout не нужно
2. Расшарить общий контекст между всеми компонентами Layout
3. Корректно стилизовать части Layout в зависимости от значения внутри общего контекста

С неймингом и стилями, думаю, всё предельно ясно. Но что насчёт контекста? На самом деле, Layout под собой содержит ещё и LayoutContext, который содержит в себе состояние компонента Sider и распространяет его на все дочерние компоненты. Схематически это выглядит примерно так:

InternalLayout  
└ LayoutContext <-- инициализируем контекст
├ Header
├ Content <-- а в этих компонентах получаем его значение
├ Footer
└ Sider


В итоге получится, что все дочерние компоненты, пытающиеся получить доступ к контексту, без обёртки работать будут криво или и вовсе не будут

С точки зрения типов всё тоже не сложно. Тот же Ant Design делает так:
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;


Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #javascript #typescript #theory #code #react #patterns
👍268🔥5🐳3
This media is not supported in your browser
VIEW IN TELEGRAM
Tree Shaking

Сам по себе Tree Shaking — это метод оптимизации кода, который позволяет автоматически удалять неиспользуемый код при сборке приложения

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


Работает всё это благодаря статической системе модулей из ES6, которая позволяет точно знать что импортирует и экспортирует каждый файл

До статической системы модулей, определить это точно было невозможно, так как импорты и экспорты могли изменяться в рантайме:


// зависящий от рантайма импорт
var my_lib;
if (Math.random()) {
my_lib = require('foo');
} else {
my_lib = require('bar');
}

// и экспорт
if (Math.random()) {
exports.baz = ···;
}

Код выше абсолютно валиден


ES6 же предложил статическую систему модулей, благодаря чему Tree Shaking и стал в целом возможен

В примере с прикреплённого видоса мы видим, как в конечный бандл из файла math.js попадает только функция multiply. Происходит это как раз потому что sum() не используется в коде нашей программы index.js

Настроить всё очень просто, например, с тем же Vite — он поддерживает Tree Shaking из коробки при сборке в прод режиме ( обычно это vite build )

Однако, стоит помнить, что не весь код и библиотеки поддерживают Tree Shaking. "Шейкаться" будет только тот код, что использует ES6 модули


Проанализировать сборку через Vite можно с помощью плагина rollup-plugin-visualizer — он покажет, какие модули включены в финальную сборку, и сколько они весят

Спасибо за прочтение, это важно для меня

@prog_way_blogчат#web #vite #theory
🔥30👍8🐳41
Как скопировать значение в буфер обмена

Часто может возникнуть необходимость скопировать какое-то значение в буфер обмена, например, при нажатии на кнопку, и есть два способа сделать это:

Современный метод использует navigator.clipboard. Это браузерное API, которое предоставляет асинхронные методы для чтения и записи данных в буфер обмена

navigator.clipboard.writeText('Какой-то текст')


Этот метод прост, но есть один важный нюанс: он работает только в безопасных контекстах (например, на страницах, загруженных по HTTPS). Проверить это можно с помощью флага 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


Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #javascript #theory #data
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥38👍5🐳54
Теги для шаблонных строк

В 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, конечно же, с более сложной логикой внутри

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

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

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #javascript #theory #data #code
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍98😐2🥰1🐳1
progway — программирование, IT
Теги для шаблонных строк В JavaScript есть, как по мне, крайне странный синтаксис. Самым очевидным его применением можно считать styled-components и выглядит всё это примерно так: const display = 'flex'; const Button = styled.button` padding: 10px; …
Ещё один пример

Бывает такое, что нужно встроить в строку значение, которое может быть пустым. Обычно пишутся доп. проверки:
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


Ну типа костыль. А вроде и нет. Просто ещё один пример посмотреть как это можно применить

@prog_way_blogчат#javascript #code
🔥14👍64🐳4
Связываем React и localStorage через useSyncExternalStore

Как согласовать изменение состояния в реакте и поля в localStorage? До недавнего времени самым простым вариантом было создать контекст с внутренним React состоянием и обрабатывать всё взаимодействие с localStorage через него — вариант рабочий, но далеко не идеален: легко напороться на ререндеры, много кода писать нужно ну и вот это вот всё

Также можно обработать какое-то не-реактовое значение через комбинацию useState + useEffect, но это ещё менее надёжно, ведь браузерные значения могут меняться и без уведомления реакта, и, соответственно, без ререндера

Красиво в одной из статей на хабре описали:

Для работы с состоянием в React используются хуки useState и useReducer, но они не умеют работать с состоянием, которое "живет" за пределами React, поскольку в один момент времени доступна только одна версия внешнего состояния.

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

Статья: https://habr.com/ru/companies/timeweb/articles/720136/


Но не так давно в 18 версию React добавили хук useSyncExternalStore, который такую задачу решает намного изящнее

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

Короче, что это за хук вообще? Очень просто — этот хук нужен для более глубокой интеграции внешних хранилищ в модель React. Говоря проще — хук нужен для того, чтобы триггерить рендер из внешних хранилищ, а не только через 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;
};


В чём тут идея:
1. При вызове updateStore будем помимо изменения значения в localStorage диспатчить на window ещё и StorageEvent с ключом, например, "update-local-storage"
2. В функции подписки subscribe объясним когда нужно вызывать getSnapshot для получения актуального состояния из внешнего хранилища и когда от его прослушивания нужно отписаться. Можно воспринимать как эффект

Использовать будем как обычный useState:
const [name, setName] = useLocalStorageState("name", "progway");


Теперь хук при вызове с одним и тем же ключом к localStorage (name в примере выше) будет обновлять все зависимые компоненты при регистрации события "update-local-storage" на window

Используя тот же подход, можно реализовать порой очень полезные хуки useMediaQuery, useWindowSize и другие. О первых двух можно прочитать в статье от Timeweb Cloud

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #javascript #theory #data #code #react
Please open Telegram to view this post
VIEW IN TELEGRAM
28👍11🔥6🐳2
Галопом по теории WebRTC

Тема большая, но попробую максимально сжато рассказать в текстовом посте

WebRTC — Web Real Time Communications — стандарт, который описывает прямую передачу потоковых аудиоданных, видеоданных и контента между клиентами в режиме реального времени. На основе этого стандарта работают всякие зумы, телемосты, google meet и прочие площадки

Для реализации такого соединения в браузерах используется нативный JS класс RTCPeerConnection


Причем акцент тут на фразе "прямая передача", так как WebRTC в идеале, пусть и не всегда, представляет собой p2p соединение

p2p — peer-to-peer — это такое сетевое соединение, которое позволяет двум или более устройствам общаться между собой напрямую без сервера-посредника. Ещё такие соединения называют одноранговыми


Однако, если мы можем общаться между устройствами без посредника, это не значит, что мы можем установить соединение без посредника

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

У каждого устройства в сети гарантированно может быть только локальный IP адрес, который далее через NAT транслируется во внешнюю сеть

NAT — Network Address Translation — механизм TCP/IP, который позволяет транслировать внутренние локальные IP адреса во внешние IP адреса за пределами нашего маршрутизатора (можно назвать роутером), который будет выступать в роли межсетевого экрана (файрвол)


Короче, чтобы решить всю эту сетевую кашу, существует STUN, который позволяет каждому клиенту узнать свой публичный адрес и инициатору соединения сформировать две сущности — 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, сокеты... хоть голубями отправляйте


Немного задушил теорией и надеюсь, что нигде не ошибся, пишите если что

Спасибо за прочтение, это важно для меня ❤️

@prog_way_blogчат#web #theory #data
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥24👍75🐳3
progway — программирование, IT
пусть и не всегда
А, кстати, почему "не всегда"

Если хотя бы у одного из клиентов прям очень медленный интернет или есть ещё какие-то проблемы, которые не позволяют открыть p2p соединение, то p2p соединения и не получится

В таких случаях соединение организуется через TURN сервер и WebRTC становится не p2p, а уже клиент-сервер-клиент способом коммуникации

TURN — Traversal Using Relay NAT — это протокол, который позволяет узлу за NAT или брандмауэром получать входящие данные через TCP или UDP соединения


Ну, вроде выговорился
🔥17👍53🐳3
Вот вам ещё визуализация всего, что я пытался описать текстом
🔥18👍5🐳3🤯1
Набор каналов об IT

В телеге есть функция зашарить сразу целую папку с каналами, хочу поделиться с вами одной такой:

🔘Каналы о пересечении профессии, жизни, разных увлечений

🔘Фронтенд, мобильная разработка, ИИ модельки и другие сферы

🔘Новости, экспертный контент и блоги

Добавляйте папку к себе, каналов много, каждый точно найдёт что-то для себя

Добавить папку можно по ссылке

@prog_way_blogчат
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7🐳52🤬21👍1
Флоу рендеринга компонента в 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
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥34🐳511👍1
🔖Оно живоеReact 19 вышел в стабильной версии, о чём и сообщает команда реакта

Ну, верим

@prog_way_blog#news
Please open Telegram to view this post
VIEW IN TELEGRAM
🎉19🐳51👍1🤯1💯1
2025/07/09 02:29:45
Back to Top
HTML Embed Code: