Реакт – хлам, и я вам это докажу!
Современные фронтенд-фреймворки обещают вам быструю разработку, простую интеграцию и избавление от всех возможных проблем. Но самом деле обычно вы получаете совсем другое. Обсудить Статья публикуется в переводе, автор оригинального текста Джейсон Найт. Во всех этих ваших модных React, Vue и Angular нет никакого смысла. На стороне сервера они не делают ничего такого, с чем не могли бы справиться шаблонные строки – причем гораздо чище и эффективнее. Как будто HTML для вас слишком сложен, и вы решили усложнить его еще больше. На клиентской стороне фреймворки разрушают юзабельность и доступность, так как многие важные вещи (вроде корзины покупок) просто не могут работать без JavaScript и не имеют никакой адекватной “изящной деградации”. Хуже того, они скрывают реальные взаимодействия с DOM и добавляют вашим приложениям ненужную сложность. Конечно, некоторых из этих проблем можно избежать – смотри Gatsby – но это только еще больше запутывает то, что без фреймворков можно сделать проще, чище и понятнее. Фронтенд-фреймворки в лучшем случае вас дезинформируют, а в худшем – нагло лгут! Во что вы верите? Ха! Нет никакого очевидного преимущества их утомительного “виртуального DOM” перед прямым изменением обычного DOM. Это даже медленнее, потому что требуется проанализировать изменения, прежде чем все равно внести их в живой документ. Просто возьми и измени! 100% ложь! Вы в любом случае собираетесь поместить их туда, и не имеет значения реально или “виртуально”. Это не влияет не только на скорость, но и на безопасность. Серьезно? Вы сравниваете простое дерево объектов с мешаниной кода, свойственной всем фронтенд-фреймворкам? Эти странные утверждения о том, что ванильный код “сложный и непонятный” происходят из какого-то иррационального страха разработчиков перед объектами. Вам говорят – “ты слишком тупой для всего этого” – и вы верите. *** Эти и многие другие утверждения фронтенд-фреймворков в конечном счете сводятся к одному и тому же. Вам предлагают писать больше кода более сложным способом и говорят, что это “проще” и “лучше” чем ванильные эквиваленты. Да кому нужны эти ваши HTML, CSS, JavaScript? Легко! Возьмем два самых “мясистых” примера из ранних туториалов React: крестики-нолики и калькулятор температуры. В них достаточно логики, и при этом они не являются критичными компонентами как форма контактов или корзина, а значит могут на 100% полагаться на JS без фоллбэков. Фреймворки обычно ценят за то, что можно сделать самостоятельно с нуля за пару минут, что мы и сделаем. Первая функция – Первым параметром передается тег, а вторым либо массив дополнительных инструкций для создания дочерних элементов, либо объект с рядом опциональных свойств: Для большей надежности можно добавить еще поле Пример использования Добавим элемент Выглядит довольно просто, а главное наглядно. Эта функция использует еще два маленьких вспомогательных метода: Помимо этого нам понадобится еще одна функция для очистки: Она удаляет Эти 4 маленьких функции покрывают 80% всех задач при работе с DOM. А теперь начнем! Вариант React: https://codepen.io/gaearon/pen/gWWZgR?editors=0010 Вариант Vanilla: https://codepen.io/jason-knight/pen/qBqwrwo Проблемы оригинала – отсутствие ясности кода и нерациональное использование DOM. Часть этих проблем не связана с JavaScript, но явно говорит о профессиональном уровне разработчиков подобных систем. Например, вместо бессмысленного Хуже всего то, что вы просто не видите реальные записи в DOM и должны на 100% полагаться на их код и доверять ему. Говорят, что хранение стейта и виртуальный DOM – это чисто и просто, но это утверждение совсем неочевидно. Ванильный вариант начинается с объявления всех переменных, необходимых для отслеживания состояния игры. Если вас беспокоит большое количество глобальных данных, то во-первых код React тоже так делает, а во-вторых – просто оберните все это в IIFE. Что касается истерических воплей о “побочных эффектах”, то запомните уже: сделанное осознанно не является побочным эффектом. Доступ к глобальной области видимости – это не “чистое зло” как вам постоянно твердят. Но если это реально очень вас расстраивает, вы вольны потратить кучу времени, чтобы написать тот же код в ООП-стиле и везде рассовать свой любимый Объявление всех переменных в одном месте очень удобно для работы. Гораздо удобнее, чем их разбрасывание по всему коду. Pascal/Modula/Ada в этом плане просто молодцы. Переменная Назначение переменных Создаем дополнительный Теперь делаем кнопки для перехода к каждому этапу игры напрямую. Простую функцию Каждая кнопка может хранить значение, и здесь очень удобно, что у элемента Функция Теперь создаем кнопку Вернуться к началу и инициализируем игру: Теперь нужен обработчик для кликов по сегментам игрового поля: Просто берем элемент, на котором было вызвано событие, и смотрим, есть ли у него Если история ходов длиннее, чем индекс текущего хода (то есть пользователь решил “переходить”), то нужно стереть все, что было дальше, и записать новый ход. Наконец проверяем, есть ли победитель. Вдруг после этого хода игроку удалось построить целую линию. Функция для проверки очень простая и гораздо красивее, чем в React-варианте: Также здесь проверяется последний ход и выполняется переход хода к следующему игроку. Нам еще нужна функция для перехода к конкретному ходу. В React-версии они на каждом ходу сохраняют все игровое поле. У нас другой подход – перезапуск игры с последовательным подключением выбранных полей. Это работает намного быстрее, чем вся эта чепуха с состоянием, даже несмотря на то, что мы все сбрасываем и начинаем по сути сначала. И наконец последняя функция: Не очень красивая, можно и почистить, но по сравнению с оригиналом вполне себе. Размер оригинала 3247 байт, а ванильная версия весит 3354 байта. Однако не забывайте, что оригинал еще использует сам React. Если убрать все комментарии и вспомогательные функции, получится 1956 байт, так что это абсолютная победа. При этом в ванильной программе вы контролируете каждую строчку кода, а в оригинале основная функциональность библиотеки от вас скрыта. React vs Vanilla Этот пример еще проще и еще менее продуман. Здесь нам также потребуются вспомогательные “библиотечные” функции (кроме purge). React-версия: https://codepen.io/gaearon/pen/WZpxpz?editors=0010 Vanila-версия: https://codepen.io/jason-knight/full/OJbGmoN С точки зрения HTML тут сразу все плохо: они используют Весь этот мусорный JSX только раздувает ваш код и делает вещи сложнее, а не проще! Хуже того, они захаркодили преобразования вместо создания объекта с несколькими преобразованиями. Это тот самый случай, когда объекты и массивы делают код проще и эффективнее. Только посмотрите на это спагетти. Для каждого преобразования своя функция, обработчики и бесконечная цепочка мусора “функционального программирования”, которая лишь добавляет накладные расходы. А ведь для всего этого хватило бы одного крошечного обработчика и одного ссылочного объекта! Сюда гораздо проще добавить новую температурную шкалу чем в оригинал, не так ли? Для примера мы добавили шкалу по Кельвину. Для создания и оформления набора полей используем уже знакомую функцию Здесь у нас Мы используем В итоге получается вот такая разметка (хотя вы и сами уже должны были это понять): Этот фрагмент добавляется в корневой Естественно, нам нужен обработчик события Получаем input, на котором было вызвано событие и определяем шкалу, к которой он привязан ( Обратите внимание на свойство В завершение проверяем, достигнута ли температура кипения воды: Меняем лишь один крошечный Вот в общем и все. React-оригинал весит 2441 байт, а vanilla-версия 2630 байт. Но опять же – у нас есть комментарии, вспомогательные функции и лишняя шкала по Кельвину. Удалим это все и получим всего 1243 байта! При этом код проще для понимания, легче масштабируется и более эффективен, потому что мы вырезали весь этот мусорный виртуальный DOM. React vs. Vanilla *** Методология React заставляет вас писать в два раза больше кода чем необходимо для решения задачи (это если не считать библиотечные функции). Это просто абстракция ради абстракции! Хватит уже верить в то, что фреймворки делают разработку лучше! Продолжение следует…Ложь
Прямое взаимодействие с “живым” DOM медленно!
Не храните данные в DOM, это небезопасно!
DOM слишком сложен для нормальных людей
Докажи!
Make и другие библиотечные функции
make
. Она похожа на знакомый вам create
из React, но может принимать JSON, чтобы создавать за один раз большие DOM-деревья. Это примерно эквивалентно тому, во что компилируется JSX.
function make(tagName, data) { var e = document.createElement(tagName); if (data) { if ( data instanceof Array || data instanceof Node || ("object" !== typeof data) ) return makeAppend(e, data), e; if (data.append) makeAppend(e, data.append); if (data.attr) for ( var [name, value] of Object.entries(data.attr) ) setAttribute(e, name, value); if (data.style) Object.assign(e.style, data.style); if (data.repeat) while (data.repeat[0]--) e.append( make(data.repeat[1], data.repeat[2]) ); if (data.parent) data.parent.append(e); } return e; }
append
с той же логикой для добавления потомков рекурсивно, attr
с атрибутами,style
для установки стилей,repeat
для создания группы элементов,parent
для указания родительского элемента.placement
, чтобы указать конкретное место размещения внутри родителя.thead
внутрь таблицы table#test
, а него tr
с несколькими ячейками th
:
make("thead", { append : [ [ "tr", [ [ "th", { scope : "col", append : "Item" } ], [ "th", { scope : "col", append : "Quntity" } ], [ "th", { scope : "col", append : "Unit Price" } ], [ "th", { scope : "col", append : "Total" } ] ] ] ], parent : document.getElementById("test") ] );
function makeAppend(e, data) { if (data instanceof Array) { for (var row of data) { e.append(row instanceof Array ? make(...row) : row); } } else e.append(data); } function setAttribute(e, name, value) { if ( value instanceof Array || ("object" == typeof value) || ("function" == typeof value) ) e[name] = value; else e.setAttribute(name === "className" ? "class" : name, value); }
makeAppend
похожа на Element.append
, но принимает массив того, что нужно добавить. Если в потоке данных она встречает массив, то передает его обработку функции make
.setAttribute
– это прокачанный Element.setAttribute
, способный принимать не только строки. Если он получает массив, объект или функцию, то назначает их напрямую как свойства элемента. Также мы заменяем атрибут className
на class
, как и оригинальный React.
function purge(e, amt) { var dir = amt < 0 ? "firstChild" : "lastChild"; amt = Math.abs(amt); while (amt--) e.removeChild(e[dir]); }
amt
потомков с конца родительского элемента e
. Если передать отрицательное число, то потомки будут удаляться с начала.Tic Tac Toe
div.board-row
в их сгенерированной разметке куда правильнее было бы использовать группу полей fieldset
. А этот state
отслеживает массу ненужных дополнительных данных, в то время как достаточно записывать только ходы – это будет быстрее и чище.Состояние игры
this
.
var lines = [ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ], [ 0, 3, 6 ], [ 1, 4, 7 ], [ 2, 5, 8 ], [ 0, 4, 8 ], [ 2, 4, 6 ] ], player, squares = make('fieldset', { repeat : [ 9, "input", { attr : { onclick : squareClick, type : "button" }, } ], attr : { id : "board" }, parent : document.body, }).elements, turn, turnHistory = [], turnOL = make("ol"), txtPlayer = new Text(), txtTurn = new Text(), winner;
squares
– это ссылка на нативную коллекцию fieldset#board.elements
, содержащую элементы игрового поля. Каждый элемент – простой input
, для которого вместо textContent
можно использовать value
. Чуть меньше кода, чуть легче манипуляции. Также устанавливаем обработчик кликов squareClick
– реализация будет чуть позже.player
, turn
, winner
, turnHistory
, turnOl
должно быть вполне очевидно. txtPlayer
и txtTurn
содержат ссылки на текстовые узлы. Обратите внимание, конструктор new Text()
– это новый document.createTextNode
.div
, в котором будет находиться вся информация о состоянии игры:
make('div', { append : [ txtTurn, " : ", txtPlayer, turnOL ], parent : document.body });
История ходов
turnButton
можно вызывать после каждого хода, а также в начале игры для создания кнопки “Перейти к началу”.
function turnButton(append, onclick, value) { make("li", { append : [ [ "button", { attr : { onclick, value }, append } ] ], parent : turnOL }); }
button
текст не связан с value
. Именуя аргументы в соответствии с названиями свойств и атрибутов, мы можем использовать краткий синтаксис создания объекта.restart
возвращает исходное состояние игры:
function restart() { for (var square of squares) square.value = ""; txtPlayer.textContent = player = "X"; winner = false; turn = 0; txtTurn.textContent = "Next Player"; }
turnButton("Go To Game Start", restart); restart();
Обработка хода
function squareClick(e) { e = e.currentTarget; if (winner || e.value) return; e.value = player; if (turnHistory.length > turn) { purge(turnOL, turnHistory.length - turn); turnHistory = turnHistory.slice(0, turn); } turnHistory.push(e); turn++; turnButton("Go to move " + turn, goToTurn, turn); calcWinner(); }
value
. Если нет, то сохраняем в него текущего игрока.Проверка победителя
function calcWinner() { for (var [a, b, c] of lines) if ( (player == squares[a].value) && (player == squares[b].value) && (player == squares[c].value) ) { txtTurn.textContent = "Winner"; return txtPlayer.textContent = winner = player; } if (turn == 9) { txtTurn.textContent = "Tie"; txtPlayer.textContent = "Game Over"; } else nextPlayer(); }
lines
не хранится внутри функции, а вынесен в глобальную область, поэтому его не нужно создавать каждый раз, тратя на это память.value
поля с текущим игроком, условие получилось гораздо проще.for...of
позволяет выполнять деструктуризацию прямо цикле.false
, перестаньте бороться с тем, что JS пытается сделать проще!Переходы по истории
function goToTurn(e) { restart(); for (var input of turnHistory) { input.value = player; if (++turn == e.currentTarget.value) break; nextPlayer(); } calcWinner(); }
Переход хода
function nextPlayer() { txtPlayer.textContent = player = player === "X" ? "O" : "X"; }
Сравнение
Калькулятор температуры
fieldset
и legend
для того, что должен делать label
. Ужасные люди!Преобразования
var scales = { celcius : { fahrenheit : (t) => 32 + t * 1.8, kelvin : (t) => 273.15 + t }, fahrenheit : { celcius : (t) => (t - 32) / 1.8, kelvin : (t) => 273.15 + ((t - 32) / 1.8) }, kelvin : { celcius : (t) => t - 273.15, fahrenheit : (t) => (t - 273.15) * 1.8 + 32 } },
Представление
make
:
root = make("fieldset", { append : [ [ "legend", [ "Enter a temperature in any field below for conversion" ] ] ], parent : document.body }), boilingText = new Text();
fieldset
с правильной легендой, которая отражает реальный смысл всего этого элемента.
function makeTempInput(name, parent) { Object.defineProperty(scales[name], "input", { value : make("input", { attr : { name, oninput : onTempInput, pattern : "[-+]?[0-9]*[.,]?[0-9]+", type : "number" }, }) }); make("label", { append : [ name, ["br"], scales[name].input, ["br"] ], parent }); }
Object.defineProperty
, чтобы сохранить ссылку на поле ввода в неперечисляемом свойстве объект шкалы.
<label> fahrenheit<br> <input id="temp_fahrenheit" name="fahrenheit" oninput="ontempinput();" pattern="[-+]?[0-9]*[.,]?[0-9]+" type="number" ><br> </label>
fieldset
, но ссылка на input
уже сохранена в объекте соответствующей шкалы.
for (var name in scales) makeTempInput(name, root);
input
:
function onTempInput(event) { var input = event.currentTarget; for ( var [name, method] of Object.entries(scales[input.name]) ) scales[name].input.value = method(input.valueAsNumber); boilNoticeUpdate(); }
input.name
). Для всех связанных с ней шкал выполняем преобразования и обновляем значения.valueAsNumber
– удобный способ сразу получить значение в виде числа.
function boilNoticeUpdate() { boilingText.textContent = scales.celcius.input.value >= 100 ? "" : "not"; }
textNode
, а не всю строку!Сравнение
- 2 views
- 0 Comment
Свежие комментарии