Поиск по метке: JavaScript
JSX и вветвления ― jsx-control-statements
Одна из раздражающих меня в React вещей, это помесь XML и JS синтаксиса. И ладно бы в одном файле, но, блин, даже в одном блоке. Сочетания XML и конструкций вроде {arr.map(arr => { очень сильно бьют, как по читаемости, так и по глазам в целом. К счастью, есть возможность этого избежать, используя jsx-control-statements. Т.к. его внедрение оказалось делом не совсем тривиальным, я решил написать об этому небольшой очерк.
Для начала, что нам даёт этот плагин?
<If condition={anyCondition}>...</If>
// вместо
{anyCondition && ...}
А также
<For each="el" index="key" of={arr}>...</For>
// вместо
{arr.map((el, key) => ...}
Т.е. XML-like конструкции, похожие на соответствующие им из XSLT (только более компактные). Также там есть ещё <Choice/> для switch-ей.
Ok, а как оно работает? Оно городит новые DOM-элементы? Нет. Оно ещё на уровне babel-я трансформируется в те самые && и .map. Т.е. это синтаксический сахар для эстетов. Ok, как подключить?
- npm i jsx-control-statements
- в настройки babel-я в plugins добавляем "jsx-control-statements"
Всё, работает. А как насчёт lint-инга? Оказалось, что тут тоже всё схвачено (для eslint), но нужно немного понастраивать:
- npm i eslint-plugin-jsx-control-statements
- в настройках eslint
- в plugins добавляем jsx-control-statements
- в extends добавляем jsx-control-statements
Теперь никаких неудобств. Проблем с подстветкой синтаксиса тоже никаких (если у вас вообще JSX нормально отображается, конечно).
onClick & middle-click
Столкнулся с неприятным багом/особенностью. Сразу к сути: в onclick в зависимости от используемого браузера могут как попадать клики средней кнопкой мыши, так и не попадать. В моём случае вышло так:
- Chrome 55, win ― не попадают
- Chrome 55, linux ― не попадают
- Chromium 53, linux ― попадают
- Firefox, linux ― не попадают
Столкнувшись с подобными проблемами, дабы точно знать, что дело в браузере, рекомендую тестировать на пустой странице, без какого-либо кода, отключив все расширения (--disable-extensions). Тестировать самый примитивный код. В моём случае всё свелось к onclick по любому тегу и console.log события.
Подсчёт повторений слов в файле на коленке
Примитивный скрипт на JavaScript, подсчитывающий кол-во повторений английских слов без учёта морфологии и пр. лингво-хитростей:
"use strict"; /* eslint-env es6 */
const fs = require('fs');
function calculate(source)
{
const words = source
.toLowerCase()
.replace(/[^a-z0-9'’]+/gm, ' ')
.split(/\s+/)
.filter(s => s.length)
.reduce((map, word) =>
{
if(!map.get(word))
map.set(word, 0);
map.set(word, map.get(word) + 1);
return map;
}, new Map());
return Array.from(words)
.sort((a, b) => a[1] < b[1] ? 1 : -1);
}
const [,, sourceF, destinationF ] = process.argv;
if(!fs.existsSync(sourceF))
throw new Error('Couldn\'t find "' + sourceF + '" file');
const source = fs.readFileSync(sourceF).toString();
const words = calculate(source);
console.info('Found ' + words.length + ' words.');
const str = words
.map(([word, count]) => `${word} = ${count}`)
.join('\n');
fs.writeFileSync(destinationF, str);
console.info('Count-map\'s written into "' + destinationF + '" file');
Из простых, но действенных, решений можно фильтровать все слова:
- с апострофами (Mike's, I'm, You're, Can't, Don't, You'll, etc)
- отдельно стоящие числа (или вообще все слова с числами)
Если хочется больше заморочиться, то можно сподобиться и написать морфологическую "определялку" является ли слово множественной формой какого-то из других представленных слов. Но, по сути, чем глубже закопаешься, тем очевиднее будет, что для серьёзных задач стоит взять серьёзную лингво-либу.
Что не так с Webpack server-ом и React Hot Loader-ом
Как минимум:
- Актуальная версия RHL в какой-то глубокой бете. Некоторые инструменты, такие как onsen monaca, в итоге, прибиты ногами к конкретной версии (иначе оно попросту не работает). Даже готовые пресеты для webpack-а завязаны на конкретные версии. Обратной совместимостью там и не пахнет. Даже сообщений внятных о том, что изменилось в stack trace-ах не ждите.
- Webpack server довольно хитро устроен внутри, поэтому такую связку очень сложно дебажить. Попробуйте ради интереса убрать [HMR] и [WDS] сообщения. В настройках плагинов такой опции нет. Попытка вырезать их из кода руками заставит вас детально разобраться в работе этого сервера. Ну или забить. Я забил :)
- Если ошибка произошла до первого успешного render-а, то stack trace показывает погоду на Марсе. Настоящая ошибка теряется в дебрях обёрток для ошибок.
- Если в Chrome Developer Tools для webpack's sourceMap-ов есть какая-то кривоватая, но поддержка, то в Firefox инструментах там просто хаос. Про stack trace-ы вообще молчу. Там просто ссылка строку в bundle.js.
- React Hot Loader по понятным причинам не перегружает обновления методов, которые были за-bind-ны в contructor-е компонента сами на себя. В итоге приходится перегружаться почти по любому поводу.
- Любые не тривиальные случаи приводят к необходимости обновить страницу.
Сложилось стойкое впечатление, что 3-ая версия React Hot Loader-а всё ещё слишком сырая, чтобы ей можно было пользоваться за пределами задач вёрстки. Но я бы не стал даже в их пределах. Этот инструмент оборачивает всё своими proxy-методами, чем сильно портит жизнь.
Поддержка sourceMap-ов тоже пока оставляет желать лучшего. Слишком часто при debug-инге проваливаешься невесть куда, не можешь поставить breakpoint, а то и вовсе наблюдаешь какие-то аномалии. Поддержка stacktrace-ов тоже пока далека от идеала.
Всё больше склоняюсь к разработке с ссылками на настоящие ресурсы, благо поддержка es6 в браузерах очень радует. Но вот JSX, я полагаю, нам в нативном виде в них не видать никогда. Так что вопрос, "а может к чёрту его этот JSX?" для меня лично достаточно актуален :)
Рецепт пуле-непробиваемого toJSON
Многие разработчики не раз за свою практику сталкивались с тем, что стандартная возможность упаковать объект в JSON падает с ошибкой, или, что хуже, уходит в вечный цикл. Причина тому цикличные ссылки. Пример:
const a = {};
const b = { a };
a.b = b;
JSON.stringify(a);
Получаем закономерный Uncaught TypeError: Converting circular structure to JSON. Но, что если вам, к примеру, для нужд debug-а, потребовался полунепробиваемый метод toJSON?
Хорошая новость! Мы можем его написать самостоятельно. Причём, благодаря наличию таких классов, как Map или Set, без особых ухищрений и извращений. Принцип действия алгоритма достаточно прост: всякий раз, когда мы собираемся упаковать очередной объект (а только они передаются по ссылке, и приводят к цикличности), мы должны проверить, а не упаковывали ли мы его ранее. А т.к. Set и Map в качестве ключа в Map и значения в Set понимают всё, что угодно, включая объекты, не приводя их к строковому виду, то и переложить эту работёнку можно и нужно именно на них.
Circular
Т.е. где-то внутри нашего toJSON будет:
if(map.has(obj))
return `$circular:${map.get(obj)}`;
map.set(obj, path);
Где path это путь к текущему итерируемому элементу. Таким образом, столкнувшись с объектом повторно, мы, вместо этого объекта, упакуем строку вида $circular:a.b.c. Что будет очень удобным подспорьем при разборе сложных багов.
Element-ы
Не будет лишним проверять — является ли объект DOMElement-ом, Document-ом или чем-нибудь подобным, т.к. эти сущности не просты внутри, но при этом, едва ли представляют для вас интерес:
if(
obj instanceof RegExp ||
obj instanceof Date ||
obj instanceof Element ||
obj instanceof Document
)
return obj.toString();
Получим строку вида [object HTMLDivElement].
jQuery
В случае, если вы используете jQuery, то стоит все порождённые ею jQuery-массивы превратить в обычные:
if(obj instanceof $) // jQuery-nodes
return _.map(obj, (v, idx) => { /* toJS */ });
В моём случае типовым результатом будет — ["[object HTMLDivElement]", "[object HTMLDivElement]"]. Но можно и усложнить схему, конвертируя DOM-ноды в их CSS-path (например: #section.cls[attr="value"]).
NaN & Null
Скорее всего, внутри своего toJSON вы будете использовать typeof obj === 'object'. Напоминаю о засаде — typeof null === 'object'; Не забудьте это учесть.
Про NaN — JSON.stringify(NaN) === "null". Не самый лучший вариант, не правда ли?
if(_.isNaN(obj))
return '$NaN$';
Дело в том, что JSON не умеет никаких NaN. Но для нужд дебага такие моменты могут быть критичными. Тоже самое и для undefined.