Может ли шаблон Vue иметь несколько корневых узлов (фрагментов)?
Если вы попытаетесь создать шаблон Vue без корневого узла, например:
<template>
<div>Node 1</div>
<div>Node 2</div>
</template>
Вы получите ошибку компиляции и/или выполнения, так как шаблоны должен иметь один корневой элемент.
Как правило, вы решаете эту проблему, добавляя «оболочку» div
как родитель. Этот элемент-оболочка не предназначен для отображения, он просто присутствует, поэтому ваш шаблон соответствует требованию с одним корнем.
<template>
<div>
<div>Node 1</div>
<div>Node 2</div>
</div>
</template>
Наличие такой оболочки обычно не имеет большого значения, но есть сценарии, в которых необходим шаблон с несколькими корнями. В этой статье мы рассмотрим, почему это так, и предложим некоторые возможные обходные пути ограничения.
Примечание: эта статья изначально была опубликована здесь, в блоге разработчиков Vue.js 2018/09/11.
Рендеринг массивов
В некоторых ситуациях вам может понадобиться, чтобы ваш компонент отображал массив дочерних узлов для включения в родительский компонент.
Например, для правильной работы некоторых функций CSS требуется особая иерархия элементов, например CSS grid или flex. Наличие оболочки между родительским и дочерним элементами не вариант.
<template>
<div style="display:flex">
<FlexChildren/>
</div>
</template>
Также существует проблема, из-за которой добавление элемента-оболочки к компоненту может привести к отображению недопустимого HTML-кода. Например, если вы строите таблицу, строку таблицы, <tr>
должны иметь только ячейки таблицы, <td>
для детей.
<template>
<table>
<tr>
<TableCells/>
</tr>
</table>
</template>
Короче говоря, требование одного корня означает, что шаблон проектирования компонентов, возвращающих дочерние элементы, будет невозможен в Vue.
Фрагменты
Это ограничение с одним корнем также было проблемой для React, но оно предоставило ответ в версии 16 с функцией под названием фрагменты. Чтобы использовать его, оберните свои многокорневые шаблоны в специальный React.Fragment
элемент:
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
}
Это отобразит дочерние элементы без оболочки. Есть даже аккуратный короткий синтаксис <>
:
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}
Фрагменты в Vue
Будет ли Vue-эквивалент фрагментов? Вероятно, не в ближайшее время. Причина этого в том, что алгоритм сравнения виртуальных DOM основан на компонентах, имеющих один корень. По словам участника Vue Линус Борг:
«Разрешение фрагментов требует значительных изменений в [the diffing] алгоритм… важно не только заставить его работать правильно, но и сделать его высокопроизводительным… Это довольно тяжелая задача… React ждал полной перезаписи своего слоя рендеринга, чтобы снять это ограничение. «
Функциональные компоненты с функциями рендеринга
Однако функциональные компоненты не имеют ограничения с одним корнем, поскольку их не нужно различать в виртуальном DOM, как это делают компоненты с отслеживанием состояния. Это означает, что если ваш компонент должен возвращать только статический HTML (честно говоря, маловероятно), вы можете иметь несколько корневых узлов.
Есть еще предостережение: вам нужно использовать функцию рендеринга, так как vue-loader в настоящее время не поддерживает функцию мультирута (хотя есть обсуждение об этом).
TableRows.js
export default {
functional: true,
render: h => [
h('tr', [
h('td', 'foo'),
h('td', 'bar'),
]),
h('tr', [
h('td', 'lorem'),
h('td', 'ipsum'),
])
];
});
main.js
import TableRows from "TableRows";
new Vue({
el: '#app',
template: `<div id="app">
<table>
<table-rows></table-rows>
</table>
</div>`,
components: {
TableRows
}
});
Взлом с директивами
Существует изящный хак, который вы можете использовать, чтобы обойти ограничение с одним корнем. Это включает в себя использование пользовательской директивы, дающей вам доступ к DOM. Вы вручную перемещаете все дочерние элементы из оболочки в родительскую, а затем удаляете оболочку.
До:
<parent>
<wrapper>
<child/>
<child/>
</wrapper>
</parent>
Промежуточный шаг:
<parent>
<wrapper/>
<child/>
<child/>
</parent>
После:
<parent>
<child/>
<child/>
</parent>
Немного сложно заставить это работать, поэтому замечательно, что плагин называется фрагменты просмотрасозданный Жюльеном Барбеем.
фрагменты представления
vue-fragments можно установить как плагин в ваш проект Vue:
import { Plugin } from "vue-fragments";
Vue.use(Plugin);
Этот плагин регистрирует глобальный VFragment
компонент, который вы используете в качестве оболочки в своих шаблонах компонентов, аналогично синтаксису фрагментов React:
<template>
<v-fragment>
<div>Fragment 1</div>
<div>Fragment 2</div>
</v-fragment>
</template>
Я не уверен, насколько надежен этот плагин для всех случаев использования — кажется, что он может быть хрупким — но для экспериментов, которые я проводил, он работал как шарм!
Станьте старшим разработчиком Vue в 2020 году.
Изучите и освойте знания профессионалов о создании, тестировании и развертывании полнофункциональных приложений Vue в нашем последнем курсе.