Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Сегодня я был изумлен тем, как обычно позитивное и дружелюбное сообщество Vue.js скатилось в печальное противостояние. Две недели назад создатель Vue Эван Ю опубликовал предложение (RFC) с новым функциональным API для компонентов в грядущем Vue 3.0. Сегодня критическое обсуждение на Reddit и аналогичные комментарии на Hacker News вызвали наплыв разработчиков в изначальный RFC с возмущениями, порой даже чересчур резкими. Там заявлялось примерно следующее:
- Весь код на Vue придется переписать совершенно по-новому, потому что существующий синтаксис будет убран
- Все то время, что люди потратили на изучение Vue, оказалось зря, потому что все поменяется
- Новый синтаксис оказался хуже старого, не дает внятной структуры и порождает спагетти-код
- Команда Vue выкатила большое изменение, ни с кем не посоветовавшись
- Vue превращается в React!
- Или нет, в AngularJS/Angular!
- Весь HTML теперь придется писать в одну огромную строку!
После стены негативных комментариев на Reddit вызывает удивление то, что само обсуждение RFC содержит по большей части позитивную реакцию, особенно в числе первых комментариев. В самом деле, самый первый комментарий полон восторгов.
Я был человеком, который написал этот первый комментарий. Так вышло, что я получил нотификацию о новом RFC, сразу же прочел его и обнаружил, что это как раз то, чего я бы хотел от Vue 3.0 и написал об этом комментарий в течение 15 минут после публикации RFC, чтобы выразить свою благодарность. В этом посте я надеюсь раскрыть эту тему, почему я считаю новое API такой хорошей идеей. Но сперва, позвольте ответить на заявления критиков.
Подозреваю, что многие люди взбаламутились после прочтения Hacker News или Reddit, где было много недостоверных комментариев, вводящих в заблуждение, но так и не прочли оригинал предложения. Эван уже добавил туда секцию вопросов и ответов, которая отвечает на большинство вопросов:
- Вам не понадобится ничего переписывать с нуля, если вы этого не хотите – новый синтаксис является дополнением, в то время как старый останется с нами на протяжении Vue 3.0 до тех пор, пока его активно используют. Даже если он в итоге будет убран из основного кода, его можно будет легко вернуть обратно с помощью плагинов.
- Время на обучение Vue не потрачено зря – в новом синтаксисе работают так же, как и раньше, уже знакомые вам концепции, какие как однофайловые компоненты, шаблоны и локальные стили.
- Изменение не было сделано без обсуждения – этот RFC и есть обсуждение. Новому синтаксису еще далеко до окончательного релиза.
- И нет, HTML не обязательно нужно писать как огромную строку
Был еще несколько субъективный пункт о том, что новый синтаксис порождает менее структурированный код. Я хочу продемонстрировать это на простом примере, который объяснит почему я настолько в восторге от RFC и почему я думаю, что новый подход приведет к более структурированному коду
Представьте такой компонент, который дает пользователю ввести информацию о своем питомце и обновляется по мере ввода данных. При этом:
- Текст заголовка обновляется в зависимости от имени питомца
- Цвет обводки зависит от выбранного цвета животного, включая тень, цвет которой вычисляется на основе выбранного цвета.
- Размер шрифта заголовка и стиль обводки зависят от выбранного пользователем размера животного
Внешний вид компонента
Вы можете увидеть живое демо с компонентом здесь и также посмотреть исходный код для Vue 2.x вот здесь (файл components/Vue2.vue).
Рассмотрим Javascript-часть этого компонента:
data() {
return {
petName: "",
petColor: "#000",
petSize: ""
};
},
computed: {
header: function() {
if (this.petName) {
return "My Pet " + this.petName;
}
return "Enter Pet Details";
},
petColorDarker: function() {
return tinycolor(this.petColor)
.darken()
.toString();
},
shadow: function() {
return "2px 2px " + this.petColorDarker;
},
borderStyle: function() {
switch (this.petSize) {
case "Small":
return "dotted";
case "Medium":
return "dashed";
default:
return "solid";
}
},
headerSize: function() {
switch (this.petSize) {
case "Small":
return "12px";
case "Large":
return "60px";
default:
return "30px";
}
}
}
В принципе, у нас есть какие-то данные и различные свойства, вычисляемые из этих данных. Заметьте, что во Vue 2.x нет способа положить связанные вещи вместе. Мы не можем разместить объявление petColor
рядом с вычисляемым petColorDarker
, потому что во Vue 2.x они группируются по типам.
Конечно, для такого маленького примера это не сильно важно. Но представьте пример побольше, в котором есть куски функциональности с необходимыми данными, вычисляемыми свойствами, методами и даже с парой вотчеров. Сейчас нет хорошего способа объединить связанные сущности вместе! Кто-то может подумать о миксинах или компонентов высшего порядка, но у них есть проблемы – сложно увидеть откуда берутся эти свойства, а также есть проблемы конфликта имен.
Новый синтаксис предлагает огранизацию компонентов по связанной функциональности, вместо типа значения. Это похоже на то как вы организуете файлы на своем компьютере – обычно вы не делаете отдельные папки "excel-таблицы" и "word-документы", скорее будут папки "работа" или "планирование отпуска". Давайте представим, как будет выглядеть наш компонент в предложенном новом синтаксисе (насколько это получится без возможности запустить код – дайте знать, если найдете какие-то баги):
setup() {
// Pet name
const petName = value("");
const header = computed(() => {
if (petName.value) {
return "My Pet " + petName.value;
}
return "Enter Pet Details";
});
// Pet color
const petColor = value("#000");
const petColorDarker = computed(() => {
return tinycolor(petColor.value)
.darken()
.toString();
});
const shadow = computed(() => "2px 2px " + petColorDarker.value);
// Pet size
const petSize = value("");
const borderStyle = computed(() => {
switch (petSize.value) {
case "Small":
return "dotted";
case "Medium":
return "dashed";
default:
return "solid";
}
});
const headerSize = computed(() => {
switch (petSize.value) {
case "Small":
return "12px";
case "Large":
return "60px";
default:
return "30px";
}
});
// All properties we can bind to in our template
return {
petName,
header,
petColor,
shadow,
petSize,
borderStyle,
headerSize
};
}
Заметим, что:
- Удивительно просто сгруппировать связанные сущности вместе
- Глядя на возвращаемое значение функции setup мы сразу видим, к чему у нас есть доступ в шаблоне.
Кроме того, новый синтаксис предоставляет полную поддержку Typescript, что было трудно достижимо в объектном синтаксисе Vue 2.x. А еще мы можем зарефакторить нашу логику, перенеся в переиспользуемые функции. Что-то вроде такого:
function usePetName() {
const petName = value("");
const header = computed(() => {
if (petName.value) {
return "My Pet " + petName.value;
}
return "Enter Pet Details";
});
return {
petName,
header
};
}
function usePetColor() {
const petColor = value("#000");
const petColorDarker = computed(() => {
return tinycolor(petColor.value)
.darken()
.toString();
});
return {
petColor,
petColorDarker
};
}
function petSizeToBorderStyle(sizeWrapper) {
const borderStyle = computed(() => {
switch (sizeWrapper.value) {
case "Small":
return "dotted";
case "Medium":
return "dashed";
default:
return "solid";
}
});
return { borderStyle };
}
function petSizeToHeaderSize(petSizeWrapper) {
const headerSize = computed(() => {
switch (petSizeWrapper.value) {
case "Small":
return "12px";
case "Large":
return "60px";
default:
return "30px";
}
});
return { headerSize };
}
export default {
setup() {
const { petName, header } = usePetName();
const { petColor, petColorDarker } = usePetColor();
const shadow = computed(() => "2px 2px " + petColorDarker.value);
const petSize = value("");
const { borderStyle } = petSizeToBorderStyle(petSize);
const { headerSize } = petSizeToHeaderSize(petSize);
return {
petName,
header,
petColor,
shadow,
petSize,
borderStyle,
headerSize
};
}
};
Во Vue 2.x я часто обнаруживал, что я пишу "монструозный компонент", который сложно разбить на мелкие кусочки – он не может разделиться на меньшие компоненты, потому что слишком много всего происходит на основе небольшого количества переменных состояния. Однако с новым предложенным синтаксисом видно, что можно легко выделить логику из больших компонентов в отдельные кусочки, вынести в отдельные файлы, если нужно, получив маленькие и легкие для понимания функции и компоненты.
Был ли у Vue более темный день? Скорее всего, нет. Бывшее единым сообщество вокруг проекта раскололось. Но у меня есть надежда, что люди взглянут еще раз на RFC, который ничего не ломает, поскольку позволяет людям все так же группировать сущности по типу, если им это нравится, но также позволяет намного больше – более понятный и чистый код, больше возможностей для библиотек и полная поддержка Typescript.
И наконец, при использовании open source, неплохо бы помнить, что его разработчики вкладывают немало усилий в то, что вам достается бесплатно. Чересчур резкая критика, которую мы видим сегодня, это не то что нам подходит. К счастью, неуважительные комментарии были в меньшинстве (пусть и значительном), а большинство было способно выражаться в более подобающем тоне.