Виртуальная библиотека DOM с акцентом на простоту, модульность, мощные функции и производительность.
Благодаря Browesstack за предоставление доступа к их отличным инструментам для тестирования кросс-браузера.
Английский | 简体中文 | хинди
Виртуальный DOM потрясающий. Это позволяет нам выразить представление нашего приложения как функцию своего состояния. Но существующие решения были слишком раздутыми, слишком медленными, не имели особенностей, имели API, смещенную с ООП и/или не имели особенностей, в которых мне нужны.
Snabbdom состоит из чрезвычайно простого, эффективного и расширяющего ядра, которое составляет всего ≈ 200 SLOC. Он предлагает модульную архитектуру с богатой функциональностью для расширений через пользовательские модули. Чтобы сохранить ядро простым, все несущественные функциональности делегируются модулям.
Вы можете превратить в то, что вы хотите! Выберите, выбирайте и настройте желаемую функциональность. В качестве альтернативы вы можете просто использовать расширения по умолчанию и получить виртуальную библиотеку DOM с высокой производительностью, небольшим размером и всеми функциями, перечисленными ниже.
h Функция для легко создания виртуальных узлов DOM.h Helper. import {
init ,
classModule ,
propsModule ,
styleModule ,
eventListenersModule ,
h
} from "snabbdom" ;
const patch = init ( [
// Init patch function with chosen modules
classModule , // makes it easy to toggle classes
propsModule , // for setting properties on DOM elements
styleModule , // handles styling on elements with support for animations
eventListenersModule // attaches event listeners
] ) ;
const container = document . getElementById ( "container" ) ;
const vnode = h (
"div#container.two.classes" ,
{ on : { click : ( ) => console . log ( "div clicked" ) } } ,
[
h ( "span" , { style : { fontWeight : "bold" } } , "This is bold" ) ,
" and this is just normal text" ,
h ( "a" , { props : { href : "/foo" } } , "I'll take you places!" )
]
) ;
// Patch into empty DOM element – this modifies the DOM as a side effect
patch ( container , vnode ) ;
const newVnode = h (
"div#container.two.classes" ,
{ on : { click : ( ) => console . log ( "updated div clicked" ) } } ,
[
h (
"span" ,
{ style : { fontWeight : "normal" , fontStyle : "italic" } } ,
"This is now italic type"
) ,
" and this is still just normal text" ,
h ( "a" , { props : { href : "/bar" } } , "I'll take you places!" )
]
) ;
// Second `patch` invocation
patch ( vnode , newVnode ) ; // Snabbdom efficiently updates the old view to the new state initpatchhfragment (экспериментальный)toVNodeinit крюкinsert крючкаremove крюкdestroy крюкremovedestroyЯдро Snabbdom обеспечивает только наиболее важную функциональность. Он разработан как можно более простым, но при этом быстро и расширяется.
init Ядро раскрывает только одну функцию init . Этот init принимает список модулей и возвращает функцию patch , которая использует указанный набор модулей.
import { classModule , styleModule } from "snabbdom" ;
const patch = init ( [ classModule , styleModule ] ) ;patch Функция patch , возвращаемая init , принимает два аргумента. Первый - это элемент DOM или VNODE, представляющий текущее представление. Второй - это VNODE, представляющий новый обновленный представление.
Если пройден элемент DOM с родителем, newVnode будет превращен в узел DOM, а пропущенный элемент будет заменен созданным узлом DOM. Если пройден старый Vnode, Snabbdom будет эффективно изменять его в соответствии с описанием в новом Vnode.
Любой старый пройденный VNODE должен быть полученным VNODE из предыдущего вызова для patch . Это необходимо, поскольку Snabbdom хранит информацию в VNODE. Это позволяет реализовать более простую и более эффективную архитектуру. Это также избегает создания нового старого дерева VNODE.
patch ( oldVnode , newVnode ) ; Несмотря на то, что не существует API конкретно для удаления дерева vnode из его элемента точки крепления, один из способов практически достижения этого - это предоставление комментария в качестве второго аргумента для patch , например:
patch (
oldVnode ,
h ( "!" , {
hooks : {
post : ( ) => {
/* patch complete */
}
}
} )
) ;Конечно, тогда в точке Mount Mount еще есть один узел комментариев.
h Рекомендуется использовать h для создания VNODES. Он принимает тег/селектор в качестве строки, необязательный объект данных и необязательную строку или массив детей.
import { h } from "snabbdom" ;
const vnode = h ( "div#container" , { style : { color : "#000" } } , [
h ( "h1.primary-title" , "Headline" ) ,
h ( "p" , "A paragraph" )
] ) ;fragment (экспериментальный)Внимание: эта функция в настоящее время экспериментальна и должна быть выбрана. Его API может быть изменен без основной версии.
const patch = init ( modules , undefined , {
experimental : {
fragments : true
}
} ) ;Создает виртуальный узел, который будет преобразован в фрагмент документа, содержащий данных детей.
import { fragment , h } from "snabbdom" ;
const vnode = fragment ( [ "I am" , h ( "span" , [ " a" , " fragment" ] ) ] ) ;toVNodeПреобразует узел DOM в виртуальный узел. Особенно хорошо для исправления ранее существовавшего, сгенерированного сервером HTML-контентом.
import {
init ,
styleModule ,
attributesModule ,
h ,
toVNode
} from "snabbdom" ;
const patch = init ( [
// Initialize a `patch` function with the modules used by `toVNode`
attributesModule // handles attributes from the DOM node
datasetModule , // handles `data-*` attributes from the DOM node
] ) ;
const newVNode = h ( "div" , { style : { color : "#000" } } , [
h ( "h1" , "Headline" ) ,
h ( "p" , "A paragraph" ) ,
h ( "img" , { attrs : { src : "sunrise.png" , alt : "morning sunrise" } } )
] ) ;
patch ( toVNode ( document . querySelector ( ".container" ) ) , newVNode ) ;Крюки - это способ зацепить жизненный цикл узлов DOM. Snabbdom предлагает богатый выбор крючков. Крюки используются как модулями для расширения Snabbdom, так и в обычном коде для выполнения произвольного кода в желаемых точках в сроке срока службы виртуального узла.
| Имя | Вызвано, когда | Аргументы к обратном вызове |
|---|---|---|
pre | Пластичный процесс начинается | никто |
init | vNode был добавлен | vnode |
create | Элемент DOM был создан на основе VNODE | emptyVnode, vnode |
insert | Элемент был вставлен в DOM | vnode |
prepatch | элемент собирается исправить | oldVnode, vnode |
update | Элемент обновляется | oldVnode, vnode |
postpatch | Элемент был исправлен | oldVnode, vnode |
destroy | элемент прямо или косвенно удаляется | vnode |
remove | Элемент напрямую удаляется из DOM | vnode, removeCallback |
post | Процесс патча выполняется | никто |
Следующие крючки доступны для модулей: pre , create , update , destroy , remove , post .
Следующие крючки доступны в свойстве hook отдельных элементов: init , create , insert , prepatch , обновить, update , postpatch , destroy , remove .
Чтобы использовать крючки, передайте их в качестве объекта, чтобы hook поле аргумента объекта данных.
h ( "div.row" , {
key : movie . rank ,
hook : {
insert : ( vnode ) => {
movie . elmHeight = vnode . elm . offsetHeight ;
}
}
} ) ; init крюкЭтот крючок вызывается во время процесса патча, когда был найден новый виртуальный узел. Крюк называется до того, как Snabbdom обработала узел каким -либо образом. Т.е. до того, как он создал узел DOM на основе VNODE.
insert крючкаЭтот крючок вызывается после того, как элемент DOM для VNODE был вставлен в документ , а остальная часть цикла патча выполнена. Это означает, что вы можете выполнять измерения DOM (например, использование GetBoundingClientRect в этом крючке безопасно, зная, что после этого не будет изменено ни одно элементы, которые могут повлиять на положение вставленных элементов.
remove крюк Позволяет вам зацепить удаление элемента. Крюк называется после того, как VNODE должен быть удален из DOM. Функция обработки получает как VNODE, так и обратный вызов. Вы можете контролировать и отложить удаление с помощью обратного вызова. Обратный вызов должен быть вызван после того, как крюк будет выполнять свой бизнес, и элемент будет удален только после того, как все remove крючков вызвало их обратный вызов.
Крюк запускается только тогда, когда элемент должен быть удален из его родителя - не в том случае, если он является ребенком элемента, который удаляется. Для этого см. Крюк destroy .
destroy крюкЭтот крюк вызывается на виртуальном узле, когда его элемент DOM удаляется из DOM или если его родитель удаляется из DOM.
Чтобы увидеть разницу между этим крючком и крючком remove , рассмотрите пример.
const vnode1 = h ( "div" , [ h ( "div" , [ h ( "span" , "Hello" ) ] ) ] ) ;
const vnode2 = h ( "div" , [ ] ) ;
patch ( container , vnode1 ) ;
patch ( vnode1 , vnode2 ) ; Здесь destroy как для внутреннего элемента div , так и для элемента span , который он содержит. remove , с другой стороны, запускается только на элементе div , потому что это единственный элемент, отделенный от его родителя.
Вы можете, например, использовать remove для запуска анимации, когда элемент удаляется, и использовать крюк destroy чтобы дополнительно оживить исчезновение детей снятого элемента.
Модули работают, регистрируя глобальные слушатели для крючков. Модуль - это просто словарь, отображая имена крючков для функций.
const myModule = {
create : ( oldVnode , vnode ) => {
// invoked whenever a new virtual node is created
} ,
update : ( oldVnode , vnode ) => {
// invoked whenever a virtual node is updated
}
} ;С помощью этого механизма вы можете легко увеличить поведение Snabbdom. Для демонстрации взгляните на реализации модулей по умолчанию.
Это описывает основные модули. Все модули являются необязательными. Примеры JSX предполагают, что вы используете jsx Pragma, предоставленную этой библиотекой.
Модуль класса обеспечивает простой способ динамического переключения классов по элементам. Он ожидает объекта в свойстве данных class . Объект должен отображать имена классов с логическими, которые указывают, должен ли класс оставаться или перейти на VNODE.
h ( "a" , { class : { active : true , selected : false } } , "Toggle" ) ; В JSX вы можете использовать class как это:
< div class = { { foo : true , bar : true } } />
// Renders as: <div class="foo bar"></div>Позволяет устанавливать свойства на элементы DOM.
h ( "a" , { props : { href : "/foo" } } , "Go to Foo" ) ; В JSX вы можете использовать props как это:
< input props = { { name : "foo" } } />
// Renders as: <input name="foo" /> with input.name === "foo"Свойства могут быть только установлены. Не удален. Несмотря на то, что браузеры разрешают добавление и удаление пользовательских свойств, этот модуль не будет предпринимать удаление. Это имеет смысл, потому что собственные свойства DOM не могут быть удалены. И если вы используете пользовательские свойства для хранения значений или ссылки на объекты на DOM, то, пожалуйста, рассмотрите возможность использования атрибутов Data-*. Возможно через модуль набора данных.
То же, что и реквизит, но устанавливают атрибуты вместо свойств на элементах DOM.
h ( "a" , { attrs : { href : "/foo" } } , "Go to Foo" ) ; В JSX вы можете использовать attrs как это:
< div attrs = { { "aria-label" : "I'm a div" } } />
// Renders as: <div aria-label="I'm a div"></div> Атрибуты добавляются и обновляются с использованием setAttribute . В случае атрибута, который был ранее добавлен/установлен и больше не присутствует в объекте attrs , он удален из списка атрибутов элемента DOM с использованием removeAttribute .
В случае логических атрибутов (например, disabled , hidden , selected ...) значение не зависит от значения атрибута ( true или false ), но вместо этого зависит от наличия/отсутствия самого атрибута в элементе DOM. Эти атрибуты обрабатываются по -разному с помощью модуля: если логический атрибут устанавливается на фальшивое значение ( 0 , -0 , null , false , NaN , undefined или пустая строка ( "" )), тогда атрибут будет удален из списка атрибутов элемента DOM.
Позволяет установить пользовательские атрибуты данных ( data-* ) на элементы DOM. Затем можно получить доступ к свойству htmlelement.dataset.
h ( "button" , { dataset : { action : "reset" } } , "Reset" ) ; В JSX вы можете использовать dataset , как это:
< div dataset = { { foo : "bar" } } />
// Renders as: <div data-foo="bar"></div>Модуль стиля предназначен для того, чтобы ваш HTML выглядел гладко и плавно оживляет. По своей сути он позволяет устанавливать свойства CSS на элементы.
h (
"span" ,
{
style : {
border : "1px solid #bada55" ,
color : "#c0ffee" ,
fontWeight : "bold"
}
} ,
"Say my name, and every colour illuminates"
) ; В JSX вы можете использовать style таким:
< div
style = { {
border : "1px solid #bada55" ,
color : "#c0ffee" ,
fontWeight : "bold"
} }
/>
// Renders as: <div style="border: 1px solid #bada55; color: #c0ffee; font-weight: bold"></div> CSS пользовательские свойства (AKA CSS -переменные) поддерживаются, они должны быть префикс с помощью --
h (
"div" ,
{
style : { "--warnColor" : "yellow" }
} ,
"Warning"
) ; Вы можете указать свойства как отложенные. Всякий раз, когда эти свойства изменяются, изменение не применяется до следующего кадра.
h (
"span" ,
{
style : {
opacity : "0" ,
transition : "opacity 1s" ,
delayed : { opacity : "1" }
}
} ,
"Imma fade right in!"
) ;Это позволяет легко декларативно оживить вход элементов.
all стоимость transition-property не поддерживается.
remove Стили, установленные в свойстве remove , вступят в силу, как только элемент будет удален из DOM. Прикладные стили должны быть анимированы с помощью CSS -переходов. Только после того, как все стили будут выполнены, будет удален элемент из DOM.
h (
"span" ,
{
style : {
opacity : "1" ,
transition : "opacity 1s" ,
remove : { opacity : "0" }
}
} ,
"It's better to fade out than to burn away"
) ;Это позволяет легко декларативно оживить удаление элементов.
all стоимость transition-property не поддерживается.
destroy h (
"span" ,
{
style : {
opacity : "1" ,
transition : "opacity 1s" ,
destroy : { opacity : "0" }
}
} ,
"It's better to fade out than to burn away"
) ; all стоимость transition-property не поддерживается.
Модуль слушателей событий дает мощные возможности для прикрепления слушателей событий.
Вы можете прикрепить функцию к событию на VNODE, предоставив объект на on с свойством, соответствующим имени события, которое вы хотите прослушать. Функция будет вызвана, когда событие произойдет, и будет передано объекту события, который принадлежит ему.
function clickHandler ( ev ) {
console . log ( "got clicked" ) ;
}
h ( "div" , { on : { click : clickHandler } } ) ; В JSX вы можете использовать on :
< div on = { { click : clickHandler } } />Snabbdom позволяет обмениваться обработчиками событий между рендерами. Это происходит без фактического касания обработчиков событий, прикрепленных к DOM.
Обратите внимание, однако, что вы должны быть осторожны при обмене обработчиками событий между VNODES , из-за техники, который использует этот модуль, чтобы избежать повторного связывания обработчиков событий с DOM. (И в целом, обмен данными между VNODES не гарантированно будет работать, поскольку модули могут мутировать данные данные).
В частности, вы не должны делать что -то вроде этого:
// Does not work
const sharedHandler = {
change : ( e ) => {
console . log ( "you chose: " + e . target . value ) ;
}
} ;
h ( "div" , [
h ( "input" , {
props : { type : "radio" , name : "test" , value : "0" } ,
on : sharedHandler
} ) ,
h ( "input" , {
props : { type : "radio" , name : "test" , value : "1" } ,
on : sharedHandler
} ) ,
h ( "input" , {
props : { type : "radio" , name : "test" , value : "2" } ,
on : sharedHandler
} )
] ) ; Для многих таких случаев вы можете вместо этого использовать обработчики на основе массива (описано выше). В качестве альтернативы просто убедитесь, что каждый узел передается уникальным on значениям:
// Works
const sharedHandler = ( e ) => {
console . log ( "you chose: " + e . target . value ) ;
} ;
h ( "div" , [
h ( "input" , {
props : { type : "radio" , name : "test" , value : "0" } ,
on : { change : sharedHandler }
} ) ,
h ( "input" , {
props : { type : "radio" , name : "test" , value : "1" } ,
on : { change : sharedHandler }
} ) ,
h ( "input" , {
props : { type : "radio" , name : "test" , value : "2" } ,
on : { change : sharedHandler }
} )
] ) ; SVG работает просто при использовании h -функции для создания виртуальных узлов. Элементы SVG автоматически создаются с соответствующими пространствами имен.
const vnode = h ( "div" , [
h ( "svg" , { attrs : { width : 100 , height : 100 } } , [
h ( "circle" , {
attrs : {
cx : 50 ,
cy : 50 ,
r : 40 ,
stroke : "green" ,
"stroke-width" : 4 ,
fill : "yellow"
}
} )
] )
] ) ;См. Также пример SVG и пример SVG Carousel.
Некоторые браузеры (например, IE <= 11) не поддерживают свойство classList в элементах SVG. Поскольку модуль класса внутри использует classList , он не будет работать в этом случае, если вы не используете полифилл ClassList. (Если вы не хотите использовать полифилл, вы можете использовать атрибут class с модулем атрибутов ).
Функция thunk принимает селектор, ключ для идентификации Thunk, функцию, которая возвращает VNODE и переменную сумму параметров состояния. В случае призывы функция рендеринга получит государственные аргументы.
thunk(selector, key, renderFn, [stateArguments])
renderFn вызывается только в том случае, если renderFn изменяется или длина массива [stateArguments] или его элементы изменяются.
key не является обязательным. Это должно быть поставлено, когда selector не является уникальным среди братьев и сестер. Это гарантирует, что Thunk всегда соответствует правильному сопоставлению при различии.
Thunks - это стратегия оптимизации, которую можно использовать, когда кто -то имеет дело с неизменными данными.
Рассмотрим простую функцию для создания виртуального узла на основе числа.
function numberView ( n ) {
return h ( "div" , "Number is: " + n ) ;
} Представление зависит только от n . Это означает, что если n не изменяется, то создание виртуального узла DOM и исправления его против старого VNODE является расточительным. Чтобы избежать накладных расходов, мы можем использовать функцию thunk Helper.
function render ( state ) {
return thunk ( "num" , numberView , [ state . number ] ) ;
} Вместо того, чтобы фактически вызовать функцию numberView , это будет разместить только фиктивное VNODE в виртуальное дерево. Когда Snabbdom устанавливает этот манекен Vnode с предыдущим Vnode, он сравнит значение n . Если n неизменен, это просто повторно использует старый Vnode. Это избегает воссоздания численного представления и процесса DIFF.
Функция представления здесь является только примером. На практике Thunks актуальны только в том случае, если вы выполняете сложный взгляд, который требует значительного времени вычислительного времени.
Обратите внимание, что фрагменты JSX по -прежнему экспериментальны и должны быть выбраны. Подробнее см. В разделе fragment .
Добавьте следующие параметры в свой tsconfig.json :
{
"compilerOptions" : {
"jsx" : " react " ,
"jsxFactory" : " jsx " ,
"jsxFragmentFactory" : " Fragment "
}
} Затем убедитесь, что вы используете расширение файла .tsx и импортируйте функцию jsx и функцию Fragment в верхней части файла:
import { Fragment , jsx , VNode } from "snabbdom" ;
const node : VNode = (
< div >
< span > I was created with JSX </ span >
</ div >
) ;
const fragment : VNode = (
< >
< span > JSX fragments </ span >
are experimentally supported
</ >
) ;Добавьте следующие параметры в конфигурацию вашего Babel:
{
"plugins" : [
[
" @babel/plugin-transform-react-jsx " ,
{
"pragma" : " jsx " ,
"pragmaFrag" : " Fragment "
}
]
]
} Затем импортируйте функцию jsx и функцию Fragment в верхней части файла:
import { Fragment , jsx } from "snabbdom" ;
const node = (
< div >
< span > I was created with JSX </ span >
</ div >
) ;
const fragment = (
< >
< span > JSX fragments </ span >
are experimentally supported
</ >
) ; Характеристики
Свойство sel указывает HTML -элемент VNODE, при желании его id , префиксированный # , и ноль или более классов, каждый из которых префикс, приготовленный . Полем Синтаксис вдохновлен селекторами CSS. Вот несколько примеров:
div#container.bar.baz - элемент div с container для идентификатора и bar классов и baz .li - элемент li без id и классов.button.alert.primary - button элемент с двумя классами alert и primary . Селектор должен быть статичным , то есть он не должен меняться в течение срока службы элемента. Чтобы установить динамический id , используйте модуль репута и для установки динамических классов. Используйте модуль класса.
Поскольку селектор статичен, Snabbdom использует его как часть идентичности VNODES. Например, если два ребенка Vnodes
[ h ( "div#container.padding" , children1 ) , h ( "div.padding" , children2 ) ] ;исправлены против
[ h ( "div#container.padding" , children2 ) , h ( "div.padding" , children1 ) ] ;Затем Snabbdom использует селектор для идентификации VNODES и переупорядочивания их в дереве DOM вместо создания нового элемента DOM. Это использование селекторов позволяет избежать необходимости указать ключи во многих случаях.
Свойство .data виртуального узла - это место для добавления информации для модулей для доступа и манипулирования реальным элементом DOM при его создании; Добавить стили, классы CSS, атрибуты и т. Д.
Объект данных - это (необязательный) второй параметр для h()
Например, h('div', {props: {className: 'container'}}, [...]) создаст виртуальный узел с
( {
props : {
className : "container"
}
} ) ; как это .data объект.
Свойство .children виртуального узла является третьим (необязательным) параметром h() во время создания. .children - это просто массив виртуальных узлов, которые следует добавлять в качестве детей родительского узла DOM после создания.
Например h('div', {}, [ h('h1', {}, 'Hello, World') ]) создаст виртуальный узел с
[
{
sel : "h1" ,
data : { } ,
children : undefined ,
text : "Hello, World" ,
elm : Element ,
key : undefined
}
] ; Как его .children .
Свойство .text создается, когда виртуальный узел создается только с одним ребенком, который обладает текстом и требует только document.createTextNode() для использования.
Например: h('h1', {}, 'Hello') создаст виртуальный узел с Hello в качестве свойства .text .
Свойство .elm виртуального узла является указателем на реальный узел DOM, созданный Snabbdom. Это свойство очень полезно для вычислений в крючках, а также в модулях.
Свойство .key создается, когда ключ предоставляется внутри вашего объекта .data . Свойство .key используется, чтобы сохранить указатели на узлы DOM, которые существовали ранее, чтобы избежать их воссоздания, если это ненужно. Это очень полезно для таких вещей, как переупорядочение списка. Ключ должен быть либо строкой, либо номером, чтобы обеспечить правильный поиск, так как он хранится внутри, как пара ключей/значения внутри объекта, где .key Крей - это ключ, а значение - это свойство .elm .
Если предоставлено, собственность .key должен быть уникальным среди элементов братьев и сестер.
Например: h('div', {key: 1}, []) создаст объект виртуального узла со свойством .key со значением 1 .
Snabbdom-это виртуальная библиотека DOM низкого уровня. Это неопинированное в отношении того, как вы должны структурировать свое приложение.
Вот некоторые подходы к созданию приложений с помощью Snabbdom.
Обязательно поделитесь им, если вы создаете приложение другим способом, используя Snabbdom.
Пакеты, связанные с Snabbdom, должны быть помечены ключевым словом snabbdom и опубликованы на NPM. Их можно найти с помощью keywords:snabbdom .
Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node':
The node before which the new node is to be inserted is not a child of this node.
Причиной этой ошибки является повторное использование VNODES между патчами (см. Пример кода), Snabbdom сохраняет фактические узлы DOM внутри узлов виртуальных DOM, передаваемых им в качестве улучшения производительности, поэтому повторное использование узлов между исправлениями не поддерживается.
const sharedNode = h ( "div" , { } , "Selected" ) ;
const vnode1 = h ( "div" , [
h ( "div" , { } , [ "One" ] ) ,
h ( "div" , { } , [ "Two" ] ) ,
h ( "div" , { } , [ sharedNode ] )
] ) ;
const vnode2 = h ( "div" , [
h ( "div" , { } , [ "One" ] ) ,
h ( "div" , { } , [ sharedNode ] ) ,
h ( "div" , { } , [ "Three" ] )
] ) ;
patch ( container , vnode1 ) ;
patch ( vnode1 , vnode2 ) ;Вы можете решить эту проблему, создав мелкую копию объекта (здесь с синтаксисом распространения объекта):
const vnode2 = h ( "div" , [
h ( "div" , { } , [ "One" ] ) ,
h ( "div" , { } , [ { ... sharedNode } ] ) ,
h ( "div" , { } , [ "Three" ] )
] ) ;Другим решением было бы обернуть общие vnodes в заводскую функцию:
const sharedNode = ( ) => h ( "div" , { } , "Selected" ) ;
const vnode1 = h ( "div" , [
h ( "div" , { } , [ "One" ] ) ,
h ( "div" , { } , [ "Two" ] ) ,
h ( "div" , { } , [ sharedNode ( ) ] )
] ) ; Получите запросы на то, чтобы сообщество могло предоставить отзыв о том, что он должен быть объединен после того, как была предоставлена такая возможность в течение нескольких дней.