Добрый день, Хабр! Решил поделиться своим небольшим, но полезным открытием в плане использования html data-attributes & css selectors.
Html data-attributes - это кастомные атрибуты, которые вы можете сами назначать куда-угодно и с каким угодно именем (но имя должно начинаться с префикса data-
). Затем вы можете использовать их в css селекторах, чтобы влиять на содержимое классов и уже классами управлять элементами. Движок браузера автоматически среагирует на изменение data-атрибута и применит соответствующий код css класса.
Код реакт компоненты, где по нажатию кнопки что-то скрывается, что-то показывается:
import React, {useState} from "react";
const ComponentWithExpandableContent = () => {
const [expanded, setExpanded] = useState(false)
return <div style={{
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'start',
padding: 32,
gap: 32,
}}>
{ !expanded && <div>Initial content</div> }
<button onClick={()=>setExpanded(!expanded)}>
{ !expanded ? 'Expand' : 'Collapse' }
</button>
{ expanded && <div>Additional content</div> }
{ expanded && <div>More additional content</div> }
</div>
}
export default React.memo(ComponentWithExpandableContent)
Состояние expanded=false
:
Состояние expanded=true
:
Теперь просто html css js
.
Файл emulating-use-state.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
Эмулируем React useState в обычном JS
(via data-attributes & css selectors)
</title>
<link rel="stylesheet" type="text/css" href="emulating-use-state.css">
</head>
<body>
<script>
function toggleExpand(){
const element = document.getElementById('component')
if (element.dataset.expanded!=='true'){
element.dataset.expanded = 'true'
} else {
element.dataset.expanded = 'false'
}
}
</script>
<div class="someElementContext">
<!-- data-expanded хранит состояние -->
<div id="component" class="component" data-expanded="false">
<div class="collapsableContent">
<div>Initial content</div>
</div>
<button onClick="toggleExpand()">
<div class="collapsableContent">Expand</div>
<div class="expandableContent">Collapse</div>
</button>
<div class="expandableContent">
<div>Additional content</div>
</div>
<div class="expandableContent">
<div>More additional content</div>
</div>
</div>
</div>
</body>
</html>
Файл стилей emulating-use-state.scss:
.someElementContext {
display: contents;
.component {
display: flex;
flex-flow: column nowrap;
align-items: start;
padding: 32px;
gap: 32px;
// Классы, управляющие видимостью элементов на странице
// в зависимости от текущего значения data-expanded
.expandableContent {
display: none;
}
.collapsableContent {
display: contents;
}
&[data-expanded=true] .expandableContent {
display: contents;
}
&[data-expanded=true] .collapsableContent {
display: none;
}
}
}
Сгенерированный средой разработки emulating-use-state.css:
.someElementContext {
display: contents;
}
.someElementContext .component {
display: flex;
flex-flow: column nowrap;
align-items: start;
padding: 32px;
gap: 32px;
}
.someElementContext .component .expandableContent {
display: none;
}
.someElementContext .component .collapsableContent {
display: contents;
}
.someElementContext .component[data-expanded=true] .expandableContent {
display: contents;
}
.someElementContext .component[data-expanded=true] .collapsableContent {
display: none;
}
Получилось поведение аналогичное описанному в реакт компоненте.
Здесь в качестве стэйта выступает кастомный data-атрибут data-expanded
- он и хранит состояние. А css селектор [data-expanded=true]
изменяет свойство display
из none
в contents
(display: contents
означает показывать содержимое элемента так, как будто самого элемента не существует) и обратно в специальных классах-обёртках .expandableContent
& .collapsableContent
, которыми можно просто обернуть любые элементы в html разметке.
Комментарии (18)
DDroll
01.01.2023 17:55+3Налицо глубокое непонимание фреймворков, их предназначения и основ их работы. А саму статью можно озаглавить "когда джун начинает читать документацию и дрожит от желания поделиться со всеми открытиями, которые он там сделал"
r_Rain Автор
01.01.2023 18:02-1Подошёл в зеркало посмотрелся, никаких непониманий на лице не нашёл. Реакт перерисовывает Virtual DOM в зависимости от состояния и пропсов. Так же здесь движок браузера перерисовывает в зависимости от состояния data-атрибутов. Если у вас своё видение, то вместе с несогласием с моим мнением прошу его в студию. Я только рад буду узнать поглубже ключевые моменты, которые вы освоили благодаря своему опыту :)
kahi4
01.01.2023 20:39У реакта уже несколько лет нет virtual dom, но это так, к слову. Но я думаю что комментарий был про то, что это просто базовые css запросы и не больше. С недавно наконец-то доехавшим :has можно вообще натворить кучу всего. Но это не заменяет фреймворки и сделать на этом логику «когда догрузятся данные с бэкэнда» хоть и можно, но понадобится js, а там можно и нормальный фреймворк накатить.
r_Rain Автор
01.01.2023 21:04Спасибо, посмотрел доки, похоже виртуальный домик остался как концепция, но не как реализация.
GoodGod
02.01.2023 07:45+2У реакта уже несколько лет нет virtual dom, но это так, к слову.
Документация с вами не согласна: https://reactjs.org/docs/faq-internals.html
Конкретно говорится что React Elements - это то что можно "потрогать" из реализации Virtual DOM в React. И еще Fibers говорится, но это вроде Internal, как их потрогать я не знаю, постоянно вижу упоминания в исходниках, но наружу вроде они не торчат.
kahi4
02.01.2023 15:25+1Это не более чем абстракция-костыль. Удобная для того чтобы описать как примерно оно работает, но я имел ввиду что до 16 реактор VDOM был как реальный концепт, сейчас это Fibers, которые очень с натяжкой работают как VDOM. Одна из идей в том что реакт умеет рендерить себя далеко не только в DOM, поэтому процессы reconciliation и render разнесены, и reconscilation ответственный за все хуки жизненного цикла и поиск того, что обновилось, а render уже что получится (react native, например).
Я копался внутри как конкретно работают хуки, они складывают свое состояние в внутренний стэк внутри fiber внутри reconciliation прохода, как и весь рендер процесс, затем очищают то, что не было затронуто этим конкретным рендером (т.е. поменялось что-то внутри и старые компоненты больше не отрендерились), в общем архитектурно это довольно далеко от "отрендерили новый vdom, сравнили со старым vdom, обновили представление", хотя на высоком концептуальном уровне с натяжкой да, это все еще vdom
dopusteam
01.01.2023 19:01А при чем тут useState вообще?
r_Rain Автор
01.01.2023 19:10-1Доброго времени суток! useState хранит состояние: должен ли показываться некий дополнительный контент на странице или нет.
Я описал поведение программы с помощью реакта (как это обычно в нём делается с помощью состояния компоненты) и с другой стороны воспроизвёл то же самое поведение но без реакта. На работе мне пришлось делать форму, у которой по нажатию кнопки должны раскрываться/скрываться некоторые поля. Но пришлось делать мне это на чистом JS, и тут пришла в голову идея удобно использовать data-атрибуты & css селекторы в связке с удобными классами-обёртками (.expandableContent & .collapsableContent). Мне показалось, что эта идея может быть полезна другим разработчикам и я решил написать свою первую статью, так что прошу не судить уж совсем строго.
kosuha666
01.01.2023 20:48Можно было изменять element.style.display свойство чтобы прятать показывать блок.
Поспешили вы с первой статьей)) да и как по мне не в статьях счастье
r_Rain Автор
01.01.2023 20:51Да, я уже кажется понял, что это слишком простые вещи для хабра, наверное не стоило из этого статью делать. Насчёт element.style.display - мне хотелось изобразить именно как я это пишу в реакте и как бы вынести функционал показа/скрытия в отдельную сущность что ли) И так я добавил div который может иметь только 2 описанных мною класса.
keystore
01.01.2023 20:52А де тада setstate?
r_Rain Автор
01.01.2023 20:58-1Доброго времени суток! setState здесь напрямую не показан, но есть функция которая переключает стейт - это toggleExpand(). При желании можно добавить функцию setExpand(expanded), которая будет иметь аналогичный функционал, но строго выставлять состояние, беря его из аргумента expand. Например:
function setExpand(expanded){ const element = document.getElementById('component') element.dataset.expanded = expanded }
yroman
02.01.2023 03:19+1Это не то же самое поведение - сокрытие элемента с помощью дисплея и появление/отсутствие его в Dom дереве. Не говоря уже о том, что скрывать что-то существенное типа Полей в форме только с помощью display: none плохая практика.
Fen1kz
01.01.2023 19:36+3Я очень разочарован этой статьей. Ожидал увидеть именно эмуляцию useState как хука, потому что как именно он реализован - вообще не очевидно. А получил бесполезное "пастав атрибут и усе заработаед", приправленное зачем-то scss. Прямо теперь хочется пойти и написать статью про эмуляцию useState в жс
funca
02.01.2023 22:27То, что вы предлагаете технически возможно, но противоречит общей концепции.
DOM и движок JS в браузере это разные компоненты, со своими структурами данных, механизмами управления памятью, API и т.п. Перебрасывая данные из одного места в другое, браузер делает массу дополнительных приседаний. Поэтому такие операции относительно медленные. Хотите увеличить перфоманс? - сводите взаимодействие к минимуму.
vDOM и компания это архитектурный хак, где все состояние предлагают хранить и пересчитывать только внутри JS (reconciliation), без необходимости каждый раз ходить за ним в DOM
за тридевять земель. Абстракция render дала возможность подкладывать снизу не только DOM, но и реализации для других, в том числе не браузерных приложений. В вашем варианте все это откатывается обратно, прямо в эпоху jQuery, когда использование data-атрибутов для хранения состояния, таки-да, считалось неплохим решением)
olegkusov
Статья ради статьи? Можно было просто кинуть ссылку на MDN по data-аттрибутам
r_Rain Автор
Возможно, но здесь я привожу сравнение между реактом и ванильным джаваскриптом. Я лично считаю этот материал полезным use case использования data-атрибутов. Ну и наконец статья полностью соответствует заголовку, так что вы не обязаны читать это, если вам не интересно, но уверен, многим эта статья покажется полезной.