Vue шаблоны на JSX

В своем последнем проекте, на работе, я экспериментирую с JSX шаблонами в Vue. Vue предлагает первоклассную поддержку JSX с практически нулевой конфигурацией, но, похоже, в экосистеме Vue JSX используется нечасто.
Вот список того, что мы получим от использования JSX с Vue. Каждый из этих пунктов подробно рассматривается ниже.
Аргумент ЗА
- Вся мощь JavaScript в вашем распоряжении.
- Нет необходимости регистрировать компоненты.
- Spread оператор на максималках.
- Именование компонентов и props-параметров.
- Отсутствие конфликтов с существующими HTML-элементами.
- Вы не привязаны к паттерну:
один файл = один компонент
.
Аргумент ПРОТИВ
- Для обычного верстальщика это выглядит чуждым.
- Отсутствие директивной управления структурой.
- Больше никаких тегов
style
в ваших однофайловых компонентах. - JSX непопулярен в экосистеме Vue.
Я собираюсь поделиться своими первоначальными мыслями об использовании JSX с Vue. Я буду выкладывать одновременно два примера шаблонов Vue и их JSX-аналогов, для сравнения.
Чтобы заставить шар катиться, вот простой пример того, как выглядит JSX в простом компоненте Vue:
<template>
<h1>{{ message }}</h1>
</template>
<script>
export default {
data: () => ({
message: "Hello, JSX!"
})
};
</script>
export default {
data: () => ({
message: "Hello, JSX!"
}),
render() {
return <h1>{this.message}</h1>;
}
};
За: Вся мощь JavaScript в вашем распоряжении.
Шаблоны Vue ограничиваются использованием только зарегистрированных компонентов. С помощью JSX можно делать всё что угодно внутри функции рендеринга.
Нет необходимости присваивать функции методам, что означает немного меньше шаблонов.
<template>
<span>{{ formatPrice(this.price) }}</span>
</template>
<script>
import { formatPrice } from "./util";
export default {
props: ["price"],
methods: {
formatPrice
}
};
</script>
import { formatPrice } from "./util";
export default {
props: ["price"],
render() {
return <span>{formatPrice(this.price)}</span>;
}
};
За: Нет необходимости регистрировать компоненты
Еще одно небольшое изменение качества жизни, которое уменьшает размер кода шаблона. Вы можете напрямую использовать ваши компоненты в функции рендеринга, без обязательной регистрации их в опции components
.
<template>
<span class="price-tag">
<formatted-price :price="price"></formatted-price>
</span>
</template>
<script>
import FormattedPrice from "./FormattedPrice";
export default {
data: () => ({
price: 100
}),
components: {
FormattedPrice
}
};
</script>
import FormattedPrice from "./FormattedPrice";
export default {
data: () => ({
price: 100
}),
render() {
return (
<span class="price-tag">
<FormattedPrice price={this.price} />
</span>
);
}
};
За: Spread оператор
В шаблонах Vue мы можем использовать v-bind
для передачи объекта в качестве prop
-параметра компонента. Пример из документации Vue:
<blog-post v-bind="post"></blog-post>
<!-- Will be equivalent to: -->
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>
Это очень похоже на синтаксис JavaScript spread, который доступен нам в JSX.
Дополнительным преимуществом синтаксиса spread является то, что его можно использовать несколько раз в компоненте. Так как v-bind
является атрибутом, он ограничен одним объявлением, а на использование spread такое ограничение не накладывается.
<template>
<!-- This doesn't work! -->
<blog-post v-bind="post" v-bind="metaData"></blog-post>
</template>
<script>
// ...
</script>
export default {
// ...
render() {
return <BlogPost {...this.post} {...this.metaData} />;
}
};
За: Именование для людей
Именование во Vue достаточно грубое, и, иногда, запутывающее.
Шаблонам нужно, чтобы всё было кебаб-кейсом, в то время как всё в вашем сценарии, вероятно, верблюжьим кейсом.
Из документации Vue:
HTML имена атрибутов не чувствительны к регистру, поэтому браузеры будут интерпретировать любые заглавные символы как строчные. Это означает, что когда вы используете шаблоны в DOM, имена props-параметров в формате camelCase должны использовать свои эквиваленты в формате шаблона (с дефисом).
Вы можете использовать PascalCase для компонентов и camelCase для props-параметров ваших файлов .vue, но тогда они не будут работать в шаблонах встраиваемых в DOM. О, и это относится только к названиям компонентов и props-ам. События должны быть написаны эквивалентно тому, как объявлены, никаких закулисных изменений кейса там нет.
<!-- App.vue -->
<template>
<!-- Компоненты могут быть вызваны с помощью PascalCase, а аттрибуты kebab-аннотацией -->
<PostList
posts="posts"
link-color="blue"
@post-click="handlePostClick"
></PostList>
<!-- Это также работает, но не в шаблонах в DOM -->
<!-- Не забывайте, что вы не можете изменить регистр событий! -->
<PostList
posts="posts"
linkColor="blue"
@post-click="handlePostClick"
></PostList>
</template>
<!-- PostList.vue -->
<script>
export default {
// Meanwhile, props should be declared with camelCase
props: ['linkColor'],
}
Все эти проблемы исчезают при использовании JSX. Поскольку вы пишете JavaScript, вы просто используете PascalCase и kebabCase везде, где пожелаете.
export default {
// ...
render() {
return (
<PostList
posts={this.posts}
linkColor="blue"
onPostClick={() => this.handlePostClick()}
/>
);
}
};
За: Отсутствие конфликтов с существующими элементами HTML
Больше дополнительных преимуществ, потому что мы отходим от HTML. Из документации Vue:
Имя, которое вы даете компоненту может зависеть от того, где вы собираетесь его использовать. При использовании компонента непосредственно в DOM (а не в строковом шаблоне или однофайловом компоненте), мы настоятельно рекомендуем следовать правилам W3C для имен пользовательских тегов (все-строчные, должны содержать дефис). Это поможет избежать конфликтов с текущими и будущими HTML-элементами.
Кроме того, если вы хотите использовать пользовательский компонент формы, вам нужно переименовать его, потому что он будет конфликтовать с существующим тегом формы. Переименовывать вещи уже по себе достаточно сложно!
<template>
<my-form></my-form>
</template>
import Form from "./Form";
export default {
render() {
return <Form />;
}
};
За: Вы не привязаны к требованию создания файла на отдельный компонент.
Иногда вам хочется написать небольшой компонент, который будет использоваться только в контексте другого компонента. С файлами .vue, вам нужно будет создать два файла, даже если второй тривиален и не должен использоваться больше нигде.
С помощью JSX вы можете структурировать вещи как угодно. Обычно я придерживаюсь одного компонента в каждом файле, но может быть полезно извлечь из него кусочки, чтобы сделать вещи более читабельными.
<template>
<article>
<post-title :title="title"></post-title>
<section>{{ post.contents }}</section>
</article>
</template>
<script>
import PostTitle from "./PostTitle";
export default {
props: ["post"],
components: {
PostTitle
}
};
</script>
<!-- PostTitle теперь можно импортировать куда угодно, в то время как мы создали его только для компонента Post -->
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
props: ["title"]
};
</script>
// Поскольку остальная часть приложения не нуждается в PostTitle, мы не должны делать этот компонент доступным другим компонентам
const PostTitle = {
props: ["title"],
render() {
return <h1>{this.title}</h1>;
}
};
export default {
props: ["post"],
render() {
return (
<article>
<PostTitle title={this.post.title} />
<section>{this.post.contents}</section>
</article>
);
}
};
Недостаток: Это выглядит пугающим для людей, которые не имели опыта работы с JSX
Если кто-то другой подготавливает вёрстку вашего приложения, вы принуждаете его к пугающей JavaScript-территории, а не к счастливой и беззаботной HTML-странице.
Это зависит от вашей команды и ситуации, стоит ли идти на компромисс.
Недостаток: Никаких директив управления структурой DOM
Возможно, вы будете скучать по Vue синтаксису рендеринга блоков и вывода их в цикле. Но, лично я, не против полностью использовать JavaScript, например, используя map
вместо v-for
.
Меньше пользовательских директив означает меньше абстракции между шаблоном и кодом, который он компилирует.
<template>
<ul>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</template>
export default {
// ...
render() {
return (
<ul>
{this.posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
};
Недостаток: Больше никаких тегов style в ваших однофайловых компонентах.
Если вы используете теги style
или хотите, чтобы в компонентах Vue присутствовали скопированные стили, вам придётся искать другое решение.
Я редко использую их сам, поэтому не могу сказать, как сейчас будет выглядеть альтернатива.
Недостаток: JSX непопулярен в экосистеме Vue.
Несмотря на то, что Vue предлагает возможность использования JSX, похоже, что в сообществе Vue оно не имеет большой популярности.
С JSX приходится делать что-то другое, в отрезе от фреймворка Vue. Обычно, проще всего держаться за большинство, когда дело касается стека, если только это не приносит существенной пользы.
Дадим шанс JSX во Vue
Я работаю над проектом, в котором достаточно много низкоуровневых компонентов. Они содержат большое количество скриптов с небольшим количеством шаблонов. В JSX чувствуется глоток свежего воздуха в этом случае.
С другой стороны, при построении больших шаблонов, состоящих из огромных кусков html с некоторыми пользовательскими компонентами и директивами, шаблоны Vue подходят лучше.
К счастью, нам не нужно выбирать один из этих вариантов, мы можем использовать оба! Я буду заниматься низкоуровневыми компонентами на JSX, а "представления", которые будут писаться другими разработчиками, будут писать с привычными шаблонами Vue.
Однозначно, стоит попробовать на одном-двух проектах, оценить, как оно, и принять решение: остаться на стороне JSX, или полностью и бесповоротно вернуться на стандартные шаблоны Vue.
Резюме
В этой статье я рассказал, что вы получите от перехода и использование JSX в шаблонах Vue. Какие преимущества и недостатки использования JSX во Vue. Так же было приведено несколько полезных примеров, того, как будут выглядеть выши Vue компоненты при переходе на JSX.