Порталы в React
Сталкивались когда-нибудь с проблемой, когда нужно рендерить элемент за пределами текущей DOM-иерархии?
Для таких задач React предлагает решение — порталы
В документации реакта приведён такой код:
Работает этот код проще простого: элемент, переданный в функцию
Такой код будет работать, но он не очень удобен, поэтому многие компонентные библиотеки максимально упрощают порталы для разработчиков и делают подобные компоненты — они есть в Chakra UI, Material UI, Semantic UI и других либах
Если максимально упростить, то можно прийти к такому варианту:
Тут стоит уточнить две детали:
1. Мы создаём новый
Если мы будем рендерить контент сразу в
2. В
Тоже полезно, чтобы лишний раз не задумываться о том, как живёт портал и не создавать ненужных элементов в вёрстке
Да и всё. Тут компонент на 10 строчек буквально, ничего сверхъестественного. Если вам нужны порталы, то задумайтесь — скорее всего вам хватит такой простой реализации
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #web #javascript #theory #code
Сталкивались когда-нибудь с проблемой, когда нужно рендерить элемент за пределами текущей 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:
Обратили внимание, как компоненты
Зачем же так сделали? Тут преследуется три цели:
1. Явно показать на уровне нейминга, что использовать
2. Расшарить общий контекст между всеми компонентами
3. Корректно стилизовать части
С неймингом и стилями, думаю, всё предельно ясно. Но что насчёт контекста? На самом деле,
В итоге получится, что все дочерние компоненты, пытающиеся получить доступ к контексту, без обёртки работать будут криво или и вовсе не будут
С точки зрения типов всё тоже не сложно. Тот же Ant Design делает так:
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #web #javascript #typescript #theory #code #react #patterns
Есть такой паттерн для реакта, который называется 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. Расшарить общий контекст между всеми компонентами
Layout3. Корректно стилизовать части
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
👍26❤8🔥5🐳3
Теги для шаблонных строк
В JavaScript есть, как по мне, крайне странный синтаксис. Самым очевидным его применением можно считать
В результате выполнения этого чуда мы получим компонент на основе нативного
Но вы когда нибудь задумывались, что
На самом деле, самый базовый пример такого синтаксиса можно рассмотреть так:
Всё, что делает эта функция — собирает строку из шаблона и подставленных переменных
—
—
Попробуем вызвать нашу функцию:
Использование обратных кавычек после именования функции вызывает эту самую функцию
По такому же принципу и работает
Этот синтаксис очень специфичный, ему не так много применений, но всё таки в некоторых случаях он бывает очень удобен. Например, в призме, с помощью такого такого синтаксиса можно кинуть запрос в БДшку
В реальной жизни вам скорее всего не понадобится писать подобные функции, но вдруг..
Спасибо за прочтение, это важно для меня❤️
@prog_way_blog — чат — #web #javascript #theory #data #code
В 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👍9❤8😐2🥰1🐳1
progway — программирование, IT
Теги для шаблонных строк В JavaScript есть, как по мне, крайне странный синтаксис. Самым очевидным его применением можно считать styled-components и выглядит всё это примерно так: const display = 'flex'; const Button = styled.button` padding: 10px; …
Ещё один пример
Бывает такое, что нужно встроить в строку значение, которое может быть пустым. Обычно пишутся доп. проверки:
Можно решить эту же задачу с помощью функции из поста выше, вот так это будет:
Просто вернем
Ну типа костыль. А вроде и нет. Просто ещё один пример посмотреть как это можно применить
@prog_way_blog — чат — #javascript #code
Бывает такое, что нужно встроить в строку значение, которое может быть пустым. Обычно пишутся доп. проверки:
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👍6❤4🐳4
Связываем React и localStorage через useSyncExternalStore
Как согласовать изменение состояния в реакте и поля в
Также можно обработать какое-то не-реактовое значение через комбинацию
Но не так давно в 18 версию
Многие скипнули его и даже не знают зачем он нужен, что, в целом, достаточно ожидаемо, ведь даже команда разработчиков позиционировала его больше как хук для разработчиков библиотек, а мы тут далеко не все пишем свои либы
Короче, что это за хук вообще? Очень просто — этот хук нужен для более глубокой интеграции внешних хранилищ в модель React. Говоря проще — хук нужен для того, чтобы триггерить рендер из внешних хранилищ, а не только через
Как раз этот хук и поможет нам интегрироваться с
На коленке код будет выглядеть примерно так:
В чём тут идея:
1. При вызове
2. В функции подписки
Использовать будем как обычный
Теперь хук при вызове с одним и тем же ключом к
Используя тот же подход, можно реализовать порой очень полезные хуки
Спасибо за прочтение, это важно для меня❤️
@prog_way_blog — чат — #web #javascript #theory #data #code #react
Как согласовать изменение состояния в реакте и поля в
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
Что такое Server-Sent Events
Часто
1. Нам нужно постоянно получать обновления с сервера
2. Не нужно постоянно отправлять что-то с клиента
Фикус в том, что держать
Для реализации понадобится только простенький эндпоинт на сервере, а далее процесс выглядит так:
1. Клиент делает
2. Сервер создаёт
3. Клиент подписывается на новое сообщение в стриме
На сервере это будет выглядеть примерно так:
На клиенте это будет выглядеть примерно так:
С таким кодом мы будем получать на клиенте сообщение "ПРИВЕТ!" каждую секунду
При этом, конечно же, никто не мешает усложнить логику со стороны сервера, и пушить новые сообщения в стрим не каждую секунду, а только при изменении данных
И конечно же никто не запрещает обернуть стрим в какой-нибудь React хук и сделать дженеричное решение для всего проекта/проектов
Если вы ни разу не работали SSE, то очень рекомендую потыкать хотя бы в песочнице — очень крутая штука!
Спасибо за прочтение, это важно для меня🥰
@prog_way_blog — чат — #theory #javascript #code #data #web
SSE — это технология для однонаправленного соединения между сервером и клиентом, которая позволяет серверу отправлять обновления данных в реальном времениЧасто
SSE могут стать отличной альтернативой WebSocket. Он отлично подойдёт для кейсов, когда:1. Нам нужно постоянно получать обновления с сервера
2. Не нужно постоянно отправлять что-то с клиента
Такая односторонняя связь полезна при реализации:
— уведомлений
— обновления данных в реальном времени (цен, загрузки CPU...)
— индикатора прогресса загрузки большого файла
— даже в играх
И многих других случаях
Фикус в том, что держать
SSE гораздо проще и дешевле, чем держать WebSocket. Как по коду, так и по перфомансуДля реализации понадобится только простенький эндпоинт на сервере, а далее процесс выглядит так:
1. Клиент делает
GET запрос на подготовленный эндпоинт через EventStream2. Сервер создаёт
event-stream, просто устанавливая нужный заголовок. Соединение не закрывается, и с этого момента сервер может пушить в стрим любые строковые данные3. Клиент подписывается на новое сообщение в стриме
На сервере это будет выглядеть примерно так:
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)
})С таким кодом мы будем получать на клиенте сообщение "ПРИВЕТ!" каждую секунду
При этом, конечно же, никто не мешает усложнить логику со стороны сервера, и пушить новые сообщения в стрим не каждую секунду, а только при изменении данных
И конечно же никто не запрещает обернуть стрим в какой-нибудь React хук и сделать дженеричное решение для всего проекта/проектов
Если вы ни разу не работали SSE, то очень рекомендую потыкать хотя бы в песочнице — очень крутая штука!
Если кратко:
SSE — технология однонаправленной связи от сервера к клиенту
С помощью SSE можно обновлять данные на клиенте в рамках одного соединения в реальном времени
Спасибо за прочтение, это важно для меня
@prog_way_blog — чат — #theory #javascript #code #data #web
Please open Telegram to view this post
VIEW IN TELEGRAM
👍39❤13🔥7🐳3
Как реагировать на изменения объекта
В JavaScript обычные объекты не умеют уведомлять о своих изменениях, однако эту задачу можно решить с помощью
Ловушек много —
Например, можно переопределить поведение объекта при обращении к какому-нибудь из его свойств:
Но это лишь частный случай, можно сделать более утилитарный пример:
Сам
В первую очередь прокси удобен конечно же для создания реактивных систем, но также его можно применять, например, для валидации свойств и логирования
Удобно, что
Спасибо за прочтение, это важно для меня❤️
@prog_way_blog — чат — #theory #useful #javascript #code #web #patterns
В JavaScript обычные объекты не умеют уведомлять о своих изменениях, однако эту задачу можно решить с помощью
ProxyProxy — это специальный встроенный в язык объект-обёртка, который позволяет изменить поведение других объектов, перехватывая действия над ними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 — обёртка, которая позволяет переопределить реакцию на операцию для объекта
— Переопределение поведения происходит при помощи "ловушек"
Спасибо за прочтение, это важно для меня
@prog_way_blog — чат — #theory #useful #javascript #code #web #patterns
Please open Telegram to view this post
VIEW IN TELEGRAM
❤28🔥15👍7🐳3🤓1