Comments 29
Как всегда, притянули за уши.
Проблема заключается в самом ключевом слове const. То, как ведут себя константы, объявленные с его помощью, не соответствует тому, что большинство разработчиков ассоциируют с понятием «константа».
"Большинство разработчиков". Покажите мне этих разработчиков. Если человек использует const и до сих пор не понял, что это значит — либо разработчик никакой (передача по ссылке? это что такое?), либо никогда и не пробовал понять. Это проблема "большинства разработчиков", но никак не языка.
Однако код, написанный с его помощью, становится сложно понять, если, применяя такой оператор, начать использовать вложенные конструкции:
let conferenceCost = isStudent ? hasDiscountCode ? 25 : 50 : hasDiscountCode ? 100 : 200;
let conferenceCost = (
isStudent ? (
hasDiscountCode ? 25 : 50
) : (
hasDiscountCode ? 100 : 200
)
);
Так понятнее? Спагетти-код можно написать на любом языке.
let eventRecord = { user: { name: "Ben M", email: "ben@m.com" }, event: "logged in", metadata: { date: "10-10-2017" }, id: "123" }; let { user: { name: userName = "Unknown" }, event: eventType = "Unknown Event", metadata: {date: eventDate}, // кто-то опечатался id: eventId } = obj;
Понять такой код практически невозможно. Эту задачу можно решить с помощью куда более удобочитаемого кода, если воспользоваться несколькими операциями деструктурирования или вовсе от них отказаться.
Я, видимо, очень странный программист, но мне этот код понятен. Опять же: сложные конструкции даны не для того, чтобы ими бездумно пользоваться, а для того, чтобы писать более понятный код. Например:
function handlePost(post) {
const [
{name: authorName, email: authorEmail},
...moreAuthors
] = post.authors;
// ...
}
Этот код понятен. Вот у нас есть пост, у него есть авторы, первого мы вынесем в переменные authorName
и authorEmail
, остальных запихнем в moreAuthors
. А теперь тот-же код без rest/spread:
function handlePost(post) {
const authorName = post.authors[0].name;
const authorEmail = post.authors[0].email;
const moreAuthors = post.authors.slice(1);
// ...
}
Сильно понятнее? Сразу поймете, почему slice(1)
? Ну… нет.
Однако на практике мне удалось выяснить, что пользоваться технологией именованного экспорта предпочтительнее по следующим причинам:
Не зря сделали одновременно и default-экспорт, и именованный. default на то и default, что содержит основной экспорт. Например, если у Вас в папке хранятся плагины, экспортирующие функцию install
:
import {install as installPlugin1} from "./plugin1";
import {install as installPlugin2} from "./plugin2";
import {install as installPlugin3} from "./plugin3";
Или:
import installPlugin1 from "./plugin1";
import installPlugin2 from "./plugin2";
import installPlugin3 from "./plugin3";
Конечно, можно сказать, что тогда сразу все функции надо было именовать installPlugin1
и т.д., но тогда переименование плагина из изменения имени файла превратится в какого-то монстра.
Ну и еще пример: плагины подгружаются автоматически, через require.context, но иногда нужно получить определенные плагины:
Вариант с export function install
:
const context = require.context("./plugins");
for(const name of context.keys()) {
context(name).install();
}
...
import {install as installPlugin1} from "./plugin1";
import {install as installPlugin2} from "./plugin2";
import {install as installPlugin3} from "./plugin3";
Вариант с export function installPluginN
:
const context = require.context("./plugins");
for(const name of context.keys()) {
Object.values(context(name))[0]();
}
...
import {installPlugin1} from "./plugin1";
import {installPlugin2} from "./plugin2";
import {installPlugin3} from "./plugin3";
Вариант с export default function install
:
const context = require.context("./plugins");
for(const name of context.keys()) {
context(name).default(); // Ну или как-то иначе, если будет "import ... from '*.js'"
}
...
import installPlugin1 from "./plugin1";
import installPlugin2 from "./plugin2";
import installPlugin3 from "./plugin3";
Хватит топить JavaScript. У Вас половина статей про то, какой JS плохой и половина про том, какой JS хороший. Найдите другую тему (CSS Paint — хороший пример).
Кстати, у хабра глюк. Когда пишешь сообщение в markdown, а затем меняешь, чекбокс markdown сбрасывается, и сообщение сохраняется, как будто написано на HTML.
Так понятнее? Спагетти-код можно написать на любом языке
Стало чуть-чуть понятнее. Но не более. Лучше избегать ребусов вроде вложенных тернарных операторов, вместо того, чтобы заниматься рисованием в текстовых файлах, имхо.
Сильно понятнее? Сразу поймете, почему slice(1)? Ну… нет.
Честно? Да, раз в 5 понятнее. Просто и по делу, без ребусов. К тому же:
function handlePost(post) {
const [firstAuthor, ...moreAuthors] = post.authors;
const { name, email } = firstAuthor;
}
Если не прибегать к вложенному деструктурированию, то получается идеальный вариант.
А целом согласен с вами по некоторым пунктам (вроде const
), да и автор несколько перегнул палку.
Я вот не соглашусь с вами и соглашусь с Ivanq по поводу деструктурирующего присваивания — мне это совсем не кажется слабой стороной языка.
Просто такие вопросы обычно решают заранее и выносят в код стайл, командный или корпоративный.
А дефолтные экспорты — зло злющее без оправдания, имхо импорты должны быть по-умолчанию именованными, и без фигурных скобок.
На самом деле, все проблемы из-за default
— когда используешь require()
, нужно постоянно добавлять .default
. Случаев, когда нужно использовать одновременно и export default, и named export — ужасно мало. Проще было бы сделать export default = module.exports
и import = require
.
Пардоньте, но с чего вы взяли, что я не люблю деструктурирующее присваивание? Я его использую почти в каждом файле по множеству раз. Одна из лучших штук в ES7. Обратите внимание на тот вариант, который я указал, как "идеальный" (по моему мнению) в комментарии выше.
Другое дело, что мне кажется, что вложенные дестр. присв. чаще зло, нежели добро. Вместо сложной матрёшки лучше написать несколько более простых дестр. присваиваний.
Вот в Вашем коде:
const [
{name: authorName, email: authorEmail},
...moreAuthors
] = post.authors;
есть строчка
{name: authorName, email: authorEmail}
Присваиваемое: слева, имя переменной справа.Это идёт вразрез с привычным синтаксисом объявления объектного литерала:
{name: authorName, email: authorEmail}
где присваиваемое: справа (очевидно, это имя переменной, чьё значение присваивается), а идентификатор, по которому впоследствии будут обращаться к этому значению: слева.Расскажите, к чему было так корёжить привычный синтаксис и сразу ли Вы привыкли, что в одном случае код надо парсить слева направо, а в другом справа налево?
Вот затем и вынесли. Для симметричности. Смотрите:
const {name: authorName, email: authorEmail} = {name: authorName2, email: authorEmail2};
Ох, это, пожалуй, мне и не нравится в деструктизации. Постоянно забываю, в каком порядке идут переменные: src: dst
или dst: src
. Странно сделали — но, видимо, чтобы легче было парсить. Сравните:
const {
authors: [
{
name: {
full: fullName
}
}
]
} = post;
(да, это скорее пример непонятного кода, но все же)
И:
const {
[
{
{
fullName: full
}: name
}
]: authors
} = post;
Чтобы понять, что речь идет об авторах, нужно дочитать до конца, или читать снизу вверх, что еще хуже.
Сразу ли привык? Нет, до сих пор не привык полностью. Мне деструктизация больше нравится, когда имя переменной и свойства совпадают, например:
const {
authors: [
{fullName},
...moreAuthors
]
} = post;
Такой код смотрится элегантно.
let i = 2;
let val =
i === 1 ? 'i is 1' :
i === 2 ? 'i is 2' :
i === 3 ? 'i is 3' :
i === 4 ? 'i is 4' :
'???';
В последнее время народ очень активно топит за prettier. Я не разделяю этих взглядов, но не удержался от того, чтобы посмотреть во что он превратит ваш code-style :)
let i = 2;
let val =
i === 1
? "i is 1"
: i === 2
? "i is 2"
: i === 3
? "i is 3"
: i === 4
? "i is 4"
: "???";
Именованный экспорт даёт возможность единообразно экспортировать из модулей всё, что угодно, в нужных количествах. Дефолтный экспорт ограничивает разработчика лишь экспортом одного значения. В качестве обходного пути тут можно применить экспорт объекта с несколькими свойствами. Однако при таком подходе теряется ценность алгоритма tree-shaking, применяемого для уменьшения размеров JS-приложений, собираемых чем-то вроде webpack. Использование исключительно модулей с именованным экспортом упрощает работу.
Субъективное мнение. Не согласен. Дефолтный экспорт дает разработчику дополнительную гибкость, например:
import {
Config900Api,
Other900Api,
System900Api,
} from 'server-api';
const MyServices = {
ConfigApi: Config900Api,
OtherApi: Other900Api,
SystemApi: System900Api,
};
export { MyServices, MagicIds };
Использование:
import { MyServices } from 'Api/Services';
function getAccessKeys(userId: number, taskUri?: string): Promise<SomeResponse> {
const params: SomeParams = { ... };
return MyServices.ConfigApi.getUser(params, new Configuration(), { credentials: { .... } })();
}
В таком случае потдержка версионности внешнего Api сводиться к изменениям в одном файле. А теперь представьте объем рефакторинга при изменении версии Api если бы вы использовали
Именованный экспорт
У ES6 безусловно хватает слабых сторон (начать с того, что до сих пор приходится пользоваться костылями типа babel чтобы на нём писать), и ни одной из них в статье не приведено. А «проблема» const — это вообще песня…
В результате, из-за того, что использование const может привести к путанице, и из-за того, что при наличии ключевого слова let наличие const выглядит избыточным, я решил всегда использовать let.
Так оно и будет.
Вангую:
Если верен принцип:
Всё что можно не писать в коде (но код при этом будет работать) — будет опущено при написании кода. ©
— пример, указание типов в JavaScript — то, если можно в функциях не думать о том что использовать? (conts или let) и код будет работать(!) — то большинство будет использовать только let.
Причем, в простых случаях она и вовсе не нужна.
Наверное, это потому что Java — язык со статической типизацией. И сериализатор заранее знает типы всех полей класса. А в JS для этого не обойтись без внешних аннотаций или Typescrpit + Reflect Metadata.
А с другой стороны можно сказать, что это в Java нет "просто объектов". И нужно заводить классы чтобы просто работать со структурами данных.
А потом на собеседованиях куча «гениальных» разрабочиков несёт ахинею.
Это:
let userName = eventRecord.user.userName || 'Unknown';
ни разу не эквивалентно этому:
let { name: userName = "Unknown" } = user
Потому что первый случай вернёт 'Unknown' для любого falsy значения, а второй – только и исключительно для undefined.
Эти высосанные из пальца статьи о том, что «если писать плохой код, то код получается плохим» раздражают с каждым разом всё больше, ей-богу.
JavaScript ES6: слабые стороны