Всем привет, решил завести канал с заметками о современной фуллстак-разработке. Костыли, фишечки, находки и боль трудовых будней.
И начнем мы с рефакторинга...
Есть в нашем React приложении текстовый редактор(WYSIWYG на минималках), написан он на SlateJS. SlateJS - оч крутая либа, я бы даже сказал фреймворк(со встроенной поддержкой React'а) для сложных текстовых редакторов типа гугл доков. Он полностью на TS, неплохой API, правда дока немного хромает, но это ниче, после DraftJS это как глоток свежего воздуха.
Так вот, прилетела таска - добавить новый функционал, пофиксить пару мелких багов, реюзнуть в новых местах, ну в целом все как обычно. Первым делом что я сделал - это обновил Slate до последней версии, читанул чендж логи, дабы убедится что нет breaking changes, типичный флоу обновления либы.
После апдейта начал рефачить. Разбил все по плагинам, улучшил API редактора, вроде как сделал крутой реюзабл компонент с возможностью кастомизации - убил пол дня. Ближе к часам 4 вечера начал тестить, qa у нас нет. Через минут 20 нашел, что отвалился "FakeSelection" плагин. Это такая штука которая эмулирует выделение текста, когда теряется фокус редактора. Например что бы сделать выделенный текст ссылкой нужно отобразить поле для ввода ссылки, т.к. в браузере только 1 элемент может быть в фокусе, то при вводе ссылки, фокус с редактора теряется, следовательно, и выделение текста тоже, а юзику нужно показать к чему эта ссылка будет добавлена, вот тут в дело и вступает этот плагин. Начал я инвестигать что же идет не так, потратил часа 4, нагородил каких-то хаков, откатил часть изменений, перепроверил все по 10 раз, а оно все не заводится. В 8 вчера вспомнил что перед рефакторингом я обновил Slate, откатился на старую версию и оп, все работает! РАБОТАЕТ!!! Бомбит до сих пор, кучу времени убил...
Вывод - не обновляйте либы вместе с рефакторингом, иначе у вас не будет гарантий что проблемы в вашем коде, либы иногда тоже ломаются или меняют свое поведение на которое ваш код уже завязан.
#рефакторинг #обновления #зависимости
Есть в нашем React приложении текстовый редактор(WYSIWYG на минималках), написан он на SlateJS. SlateJS - оч крутая либа, я бы даже сказал фреймворк(со встроенной поддержкой React'а) для сложных текстовых редакторов типа гугл доков. Он полностью на TS, неплохой API, правда дока немного хромает, но это ниче, после DraftJS это как глоток свежего воздуха.
Так вот, прилетела таска - добавить новый функционал, пофиксить пару мелких багов, реюзнуть в новых местах, ну в целом все как обычно. Первым делом что я сделал - это обновил Slate до последней версии, читанул чендж логи, дабы убедится что нет breaking changes, типичный флоу обновления либы.
После апдейта начал рефачить. Разбил все по плагинам, улучшил API редактора, вроде как сделал крутой реюзабл компонент с возможностью кастомизации - убил пол дня. Ближе к часам 4 вечера начал тестить, qa у нас нет. Через минут 20 нашел, что отвалился "FakeSelection" плагин. Это такая штука которая эмулирует выделение текста, когда теряется фокус редактора. Например что бы сделать выделенный текст ссылкой нужно отобразить поле для ввода ссылки, т.к. в браузере только 1 элемент может быть в фокусе, то при вводе ссылки, фокус с редактора теряется, следовательно, и выделение текста тоже, а юзику нужно показать к чему эта ссылка будет добавлена, вот тут в дело и вступает этот плагин. Начал я инвестигать что же идет не так, потратил часа 4, нагородил каких-то хаков, откатил часть изменений, перепроверил все по 10 раз, а оно все не заводится. В 8 вчера вспомнил что перед рефакторингом я обновил Slate, откатился на старую версию и оп, все работает! РАБОТАЕТ!!! Бомбит до сих пор, кучу времени убил...
Вывод - не обновляйте либы вместе с рефакторингом, иначе у вас не будет гарантий что проблемы в вашем коде, либы иногда тоже ломаются или меняют свое поведение на которое ваш код уже завязан.
#рефакторинг #обновления #зависимости
SVG иконки в React'е с сюрпризами.
Наши дизайнеры все еще юзают Скетч. Это боль для всех, он тупит, к нему нужен Zeplin и вот это вот все. Все наши иконки из скетча приходят в таком виде и мы их прогоняем через SVGR (https://github.com/gregberge/svgr) что бы получить реактовские компоненты:
SVG иконки мы немного правим руками, меняем цвет на
Это все работает до тех пор, пока на странице не появится 2 одинаковые иконки, один и тот же SVG код в разных местах дома, с разными цветами. В нашем случае в одном из мест по ховеру иконка меняет цвет, а в месте с ней меняется цвет и у второй иконки. WTF?! Разный HTML, на разных уровнях, кажется как баг в браузере или еще что...
ХЗ почему, может у вас не так, но у нас во всех иконках после скетча есть градиент
Наши дизайнеры все еще юзают Скетч. Это боль для всех, он тупит, к нему нужен Zeplin и вот это вот все. Все наши иконки из скетча приходят в таком виде и мы их прогоняем через SVGR (https://github.com/gregberge/svgr) что бы получить реактовские компоненты:
<svg viewBox="0 0 12 12" version="1.1" xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="currentColor" stop-opacity="0.85" offset="0%">
</stop>
<stop stop-color="currentColor" offset="100%">
</stop>
</linearGradient>
<path d="M336.519312,459.519575 L332.332119,459.845638 C332.08759,459.86468 331.892977,460.058279 331.872656,460.302704 L331.522088,464.519515 C331.499511,464.791086 331.272508,465 331,465 L331,465 C330.728943,465 330.50456,464.789333 330.487486,464.518815 L330.222001,460.31263 C330.206295,460.063791 330.009661,459.864554 329.761051,459.845576 L325.480744,459.51884 C325.209515,459.498136 325,459.272019 325,459 L325,459 C325,458.727823 325.209321,458.501419 325.480663,458.480109 L329.762141,458.143872 C330.010269,458.124386 330.206285,457.925368 330.221998,457.676973 L330.487418,453.481181 C330.504531,453.210655 330.728933,453 331,453 L331,453 C331.272522,453 331.499548,453.208897 331.522177,453.480478 L331.872664,457.686919 C331.892992,457.930886 332.086978,458.124255 332.331008,458.143805 L336.519397,458.479354 C336.790819,458.501099 337,458.727709 337,459 L337,459 C337,459.27213 336.790621,459.498447 336.519312,459.519575 Z" id="path-2">
</path>
</defs>
<g id="All-Blocks-Kit" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" transform="translate(-325.000000, -453.000000)">
<g id="Shape-Copy">
<use fill="currentColor" xlink:href="#path-2">
</use>
<use fill="url(#linearGradient-1)" xlink:href="#path-2">
</use>
</g>
</g>
</g>
</svg>
SVG иконки мы немного правим руками, меняем цвет на
currentColor- что бы потом через propsы или css менять цвет иконок. Рабочий флоу, что может пойти не так скажете вы?
Это все работает до тех пор, пока на странице не появится 2 одинаковые иконки, один и тот же SVG код в разных местах дома, с разными цветами. В нашем случае в одном из мест по ховеру иконка меняет цвет, а в месте с ней меняется цвет и у второй иконки. WTF?! Разный HTML, на разных уровнях, кажется как баг в браузере или еще что...
ХЗ почему, может у вас не так, но у нас во всех иконках после скетча есть градиент
linearGradient
тег которым задается цвет иконки, хотя сама иконка плоская и одного цвета. Как мы знаем что бы юзать градиент в svg ему нужно задать ID и использовать этот IDшник в fill атрибуте path'а fill="url(#linearGradient-1)".
И вот тут начинается самое интересно. Даже несмотря на то, что иконка прошла через SVGR(который рандомит IDшки в каждой иконе, что бы не было коллизий между разными иконками), при добавлении нескольких иконок на страницу эти коллизии появляются, но в рамках одной иконки. В принципе в этом нет ничего страшного, если бы не наш градиент... Из за того, что градиент аплается к нодам по IDшке, все иконки шарят одну единственную градиент ноду, даже если их в доме +100500, а когда мы меняем цвет у какой-то иконки, градиент подхватывает этот цвет и вместе с этим все иконки, которые юзают этот градиент, меняют цвет тоже, ЛОЛ. Демка тут: (https://codepen.io/Gandju/pen/JjNmBNB)
Ну а фикс на самом деле простой, выкасить градиент и использовать
В общем проверяйте и рефачте свои SVGшки, в них оч много мусора и можно словить оч неприятные баги...
#svg #баги
Ну а фикс на самом деле простой, выкасить градиент и использовать
currentColorнепосредственно в
fillатрибуте path'а:
<svg viewBox="0 0 12 12" version="1.1" xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink">
<g id="All-Blocks-Kit" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="Artboard" transform="translate(-325.000000, -453.000000)">
<path
d="M336.519312,459.519575 L332.332119,459.845638 C332.08759,459.86468 331.892977,460.058279 331.872656,460.302704 L331.522088,464.519515 C331.499511,464.791086 331.272508,465 331,465 L331,465 C330.728943,465 330.50456,464.789333 330.487486,464.518815 L330.222001,460.31263 C330.206295,460.063791 330.009661,459.864554 329.761051,459.845576 L325.480744,459.51884 C325.209515,459.498136 325,459.272019 325,459 L325,459 C325,458.727823 325.209321,458.501419 325.480663,458.480109 L329.762141,458.143872 C330.010269,458.124386 330.206285,457.925368 330.221998,457.676973 L330.487418,453.481181 C330.504531,453.210655 330.728933,453 331,453 L331,453 C331.272522,453 331.499548,453.208897 331.522177,453.480478 L331.872664,457.686919 C331.892992,457.930886 332.086978,458.124255 332.331008,458.143805 L336.519397,458.479354 C336.790819,458.501099 337,458.727709 337,459 L337,459 C337,459.27213 336.790621,459.498447 336.519312,459.519575 Z"
id="path-2"
fill="currentColor"
></path>
</g>
</g>
</svg>
В общем проверяйте и рефачте свои SVGшки, в них оч много мусора и можно словить оч неприятные баги...
#svg #баги
Все наши nodejs сервисы и пакеты(либы которые шарятся между сервисами, а иногда и фронтом, и паблишатся в npm) написаны на TS и билдятся тоже им. Под билдом я имею ввиду убрать типы и сделать валидный ES2017 код. Не транспилируем в ES5, т.к. и нода, и все последние браузеры отлично хавают ES2017, а ES5 только делает размер пакета больше. В билдах, в основном, мы используем cjs(commonjs) модули, они работают во всех версиях ноды, вебпак и прочие бандлеры их тоже саппортят. И только в последние пакеты, которые юзаются и на фронте, и на бэке, мы добавили возможность билда в ES модули (это те, которые
И вот в новом сервисе в качестве кор депенденси у нас есть Logux(это тип Redux, но с уклоном на бэк и реалтайм). Под капотом у него ES модули, и все бы ничего, но Logux не использует
И так, солюшенов у нас 2:
- форкнуть logux и добавить туда билд в cjs или `
- перейти на новый стандарт, добавить
Мы выбрали второй вариант и переход на новый стандарт. Но, как всегда, все оказалось не так-то и просто. После того, как изменили тип модулей в конфиге TSа, сбилженный код перестал запускаться. Проблема в том, что уже в es модулях нельзя заимпортить cjs через
Вывод: если хотите юзать современные модули в ноде, а особенно в NPM пакетах, билдите их с
#npm #nodejs #dependency
import {} from ''; export const a = 10;
), при этом cjs никуда не делся. Сделано это для того, что бы помочь бандлерам удалять неиспользуемый код(tree shaking). И до недавнего времени все работало замечательно. И вот в новом сервисе в качестве кор депенденси у нас есть Logux(это тип Redux, но с уклоном на бэк и реалтайм). Под капотом у него ES модули, и все бы ничего, но Logux не использует
.mjs
расширение для своих файлов, а без этого расширения нельзя зарекварить( require('esmodule')
) ES модуль из cjs, это ограничения ноды что бы юзать esm в cjs. И так, солюшенов у нас 2:
- форкнуть logux и добавить туда билд в cjs или `
.msj
` расширение для es модулей- перейти на новый стандарт, добавить
type="module"
в package.json
и юзать в билдах esmМы выбрали второй вариант и переход на новый стандарт. Но, как всегда, все оказалось не так-то и просто. После того, как изменили тип модулей в конфиге TSа, сбилженный код перестал запускаться. Проблема в том, что уже в es модулях нельзя заимпортить cjs через
impoprt { a } from 'cjsmodule'
, так как частичный импорт работает только с esm. Окей, нашли все cjs модули и порефачили импорты в import * as cjs from 'cjsmodule'
, в этом случае нода это хавает, и по сути, ассайнит все что есть в exports
переменной cjsmodule
в cjs
импорт. Но и это еще не все, т.к. в сорцах наших пакетов используется esm, а при билде мы имеем cjs, то при импорте таких либ через * as module
, дефолтный экспорт будет в module.default
, что логично, т.к. после билда export default A;
превратился в exports.default = A
. Пришлось везде поменять использование дефолтных импортов из cjs на module.default()|new module.default()
и тд. В итоге мы смогли завести сбилженный esm код. Потом еще выяснилось, что наш run local
скрипт перестал работать, т.к. ts-node-dev
не хочет работать с esm и резолверами которые мы юзаем, переехали на nodemon
и ts-node
, в итоге и это завелось. Все это заняло в районе 6 часов, и тупо для того, что бы начать использовать ОДНУ единственную либу с ES модулями внутри и без .mjs
расширения. Вывод: если хотите юзать современные модули в ноде, а особенно в NPM пакетах, билдите их с
.mjs
расширением или делайте 2 билда(cjs или esm), иначе кто-то будет страдать, МНОГО страдать.#npm #nodejs #dependency
🎮 Модули - amd, cjs, esm и umd
И еще немного про модули. Какие они были, к чему все пришло, и как это связано с предыдущим постом.
И так, начнём мы с AMD (Asynchronous module definition), это первые трушные модули на фронте с которыми мне пришлось поработать. Наверняка среди вас есть такие же динозавры как и я, которые помнят всю боль и кучу костылей
На небольших проектах это вполне работало и не доставляло больших проблем. Но с ростом проекта массивы зависимостей становились ОГРОМНЫЕ, и это еще хорошо если в компании были прописаны код-стайл гайды и их соблюдали. Т.к. в 2014 году про линтеры никто ничего особо не знал, ну либо меня это обошло стороной 😂 . И как вы уже поняли, у нас стандартов не было, была только большая боль, и если над файлом работало параллельно несколько человек, появлялись конфликты в определении зависимостей, которые было достаточно сложно резолвать(поиск в 2ух массивах по 30+ зависимостей дифов, и это только для одного файла, доходило до того, что мержили по несколько дней, когда сливали два больших фича-бранча). Код выглядел как-то так:
Да, у
Продолжение в первом коменте...
Читать в ноушен: bit.ly/3smF0pW
#node #frontend #dependencies #longread
И еще немного про модули. Какие они были, к чему все пришло, и как это связано с предыдущим постом.
И так, начнём мы с AMD (Asynchronous module definition), это первые трушные модули на фронте с которыми мне пришлось поработать. Наверняка среди вас есть такие же динозавры как и я, которые помнят всю боль и кучу костылей
RequireJS
. RequireJS
(https://requirejs.org/docs/whyamd.html) - это такая древняя JS либа, реализующая AMD спеку. Она появилась за долго до бандлеров(webpack, rollup, etc) и позвала менеджить модули и их зависимости, а также загружать все это дело в рантайме по требованию, да-да код сплитинг в 2012 годах. Но как я и сказал ранее с RequireJS
было много боли, в основном с зависимостями. Т.к. способ их определения - тупо массив строк с названиями модулей:
define('myModuleName', ['dep1', 'dep2'], function (dep1, dep2) {
return function () {
// my module code is here
};
});
На небольших проектах это вполне работало и не доставляло больших проблем. Но с ростом проекта массивы зависимостей становились ОГРОМНЫЕ, и это еще хорошо если в компании были прописаны код-стайл гайды и их соблюдали. Т.к. в 2014 году про линтеры никто ничего особо не знал, ну либо меня это обошло стороной 😂 . И как вы уже поняли, у нас стандартов не было, была только большая боль, и если над файлом работало параллельно несколько человек, появлялись конфликты в определении зависимостей, которые было достаточно сложно резолвать(поиск в 2ух массивах по 30+ зависимостей дифов, и это только для одного файла, доходило до того, что мержили по несколько дней, когда сливали два больших фича-бранча). Код выглядел как-то так:
define('myModuleName', [ "require", "jquery", "blade/object", "blade/fn", "rdapi",
"oauth", "blade/jig", "blade/url", "dispatch", "accounts",
"storage", "services", "widgets/AccountPanel", "widgets/TabButton",
"widgets/AddAccount", "less", "osTheme", "jquery-ui-1.8.7.min",
"jquery.textOverflow"],
function (require, $, object, fn, rdapi,
oauth, jig, url, dispatch, accounts,
storage, services, AccountPanel, TabButton,
AddAccount, less, osTheme) {
});
Да, у
RequireJS
был сахар-синтаксис который позволял подрубать зависимости через require
метод, но он медленнее работал, был забагованный и мы его не юзали. В общем на больших проектах мы, скорее всего и не только мы, очень страдали с RequireJS
. Страдали до тех пор пока не появились бандлеры, которые могли нормально работать с CJS модулями.Продолжение в первом коменте...
Читать в ноушен: bit.ly/3smF0pW
#node #frontend #dependencies #longread
🎛️ Новинки TS 4.4
TypeScript с каждым релизом становится все круче и интереснее! В последней бета версии(4.4) ребята добавили парочку очень крутых фич (https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-beta/). Вот краткий обзор основных изменений:
Больше всего я заценил
Как видите мы можем реюзать
В предыдущих версиях
Также теперь можно использовать символы, оч удивило что раньше TS это не саппортал, и строковые паттерны в качестве ключей объектов, что очень полезно в некоторых кейсах, например затайпать
Ошибки в
Ну и напоследок, появилась возможность сказать TSу что опциональные ключи в объектах являются ТОЛЬКО опциональными и туда нельзя засунуть
С каждым новым релизом люблю TS все больше ❤️, надеюсь и вы перешли на TS и также кайфуете ✌️
Читать в ноушен: https://bit.ly/384LwZe
#typescript #news
TypeScript с каждым релизом становится все круче и интереснее! В последней бета версии(4.4) ребята добавили парочку очень крутых фич (https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-beta/). Вот краткий обзор основных изменений:
Больше всего я заценил
Control Flow Analysis of Aliased Conditions
, благодаря этой фичи результат тайпгарда можно сохранить в переменную и дальше в коде использовать эту переменную что бы закастить тип. Если не поняли, вот пример:interface BaseOptions {
prop1: string;
prop2: number;
}
interface ClickableOptions extends BaseOptions {
anchorLabel: string
onAnchorClick: VoidFunction
}
const Component = ({ prop1, prop2, ...props }: BaseOptions | ClickableOptions) => {
const isClickable = 'anchorLabel' in props;
if (isClickable) {
const { onAnchorClick } = props;
}
// ...
const anchorLabel = isClickable ? props.anchorLabel : null;
}
Как видите мы можем реюзать
isClickable
переменную в разных местах и TS не будет ругаться что в пропах нет anchorLabel
и onAnchorClick
, ну разве не топчик? 😎 В предыдущих версиях
'anchorLabel' in props
приходилось выносить в отдельную тайпгард функцию и вызывать ее, что бы помочь TSу с типами, и это хорошо если тайпгард простой, и особо не влияет на перф. Ну или как альтернативный и не очень красивый вариант без тайпгарда, который уменьшает надежность типов - использовать ключевое слово as
:interface BaseOptions {
prop1: string;
prop2: number;
}
interface ClickableOptions extends BaseOptions {
anchorLabel: string
onAnchorClick: VoidFunction
}
const isClickable = (props: Omit<BaseOptions | ClickableOptions, keyof BaseOptions>): props is Omit<ClickableOptions, keyof BaseOptions> => 'anchorLabel' in props
// Good
const Component = ({ prop1, prop2, ...props }: BaseOptions | ClickableOptions) => {
if (isClickable(props)) {
const { onAnchorClick } = props;
}
// ...
const anchorLabel = isClickable(props) ? props.anchorLabel : null;
}
// Bad
const Component = ({ prop1, prop2, ...props }: BaseOptions | ClickableOptions) => {
const isClickable = 'anchorLabel' in props;
if (isClickable) {
const { onAnchorClick } = props as Omit<ClickableOptions, keyof BaseOptions>;
}
// ...
const anchorLabel = isClickable ? (props as Omit<ClickableOptions, keyof BaseOptions>).anchorLabel : null;
Также теперь можно использовать символы, оч удивило что раньше TS это не саппортал, и строковые паттерны в качестве ключей объектов, что очень полезно в некоторых кейсах, например затайпать
data
атрибуты в React'е стало намного проще:interface Props {
value: string;
[symbols: symbol]: number;
[data: `${'data' | 'aria'}-${string}`]: string;
}
const simple: Props = { value: '' };
const symbols: Props = { value: '', [Symbol('')]: 0 };
const dataAttr: Props = { value: '', "data-opened": 'true', "aria-label": "label" };
Ошибки в
catch
блоке имеют тип unknown
вместо any
, что делает код намного безопаснее, т.к. придется делать тайпгарды если нужно достать что-то ( err.message
) из ошибки.Ну и напоследок, появилась возможность сказать TSу что опциональные ключи в объектах являются ТОЛЬКО опциональными и туда нельзя засунуть
undefined
. Возможно эта фича покажется вам не такой уж и полезной, но она позволяет избежать таких проблем:interface Entity {
a: number;
b: string;
}
const entity: Entity = {a: 10, b: '20'};
const patch: Partial<Entity> = { a: 10, b: undefined };
const updated: Entity = { ...entity, ...patch }
updated.b.toUpperCase(); // Runtime Error: Cannot read property 'toUpperCase' of undefined
С каждым новым релизом люблю TS все больше ❤️, надеюсь и вы перешли на TS и также кайфуете ✌️
Читать в ноушен: https://bit.ly/384LwZe
#typescript #news
🎹 Массивы и Таплы
В TS/JS массивы можно разделить на 2 типа, обычные массивы и таплы (tuple). На данный момент для js-движков между ними нет разницы и обрабатываются они как массивы. На практике таплы чаще всего используются в ситуациях, когда нужно вернуть несколько(2-3) значений из функции, если нужно больше, то лучше использовать объект.
Например в React'е,
Почему тапл лучше объекта для нескольких значений? При деструкторизации тапл проще использовать чем объект, а так же с ним меньше мест в которых можно допустить ошибку, достаточно знать лишь порядок данных. С объектом же нужно точно знать какие есть ключи и каких у них данные, а при деструкторизации нужно тупо больше кода для таких широко-используемых функций как
Согласитесь вариант с таплом намного удобнее и читабельнее объекта. Но повторюсь, тапл хорош только для нескольких значений, желательно до 3. Иначе будет каша с кучей данных, разных типов, без названий. Держать такое в голове, также как и сапортать, оч сложно.
Вернемся к TS, для определения тапла, типы элементов должны быть между
Как вы заметили, таплы в TS могут иметь неопределённую длину, а также типы элементов можно задавать с конца. Кстати,
Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3kxH8aQ
#typescript #js
В TS/JS массивы можно разделить на 2 типа, обычные массивы и таплы (tuple). На данный момент для js-движков между ними нет разницы и обрабатываются они как массивы. На практике таплы чаще всего используются в ситуациях, когда нужно вернуть несколько(2-3) значений из функции, если нужно больше, то лучше использовать объект.
Например в React'е,
useState
возвращает тапл со значением и сеттером.Почему тапл лучше объекта для нескольких значений? При деструкторизации тапл проще использовать чем объект, а так же с ним меньше мест в которых можно допустить ошибку, достаточно знать лишь порядок данных. С объектом же нужно точно знать какие есть ключи и каких у них данные, а при деструкторизации нужно тупо больше кода для таких широко-используемых функций как
useState
:
// tuple
const [state1, setState1] = React.useState(0);
const [state2, setState2] = React.useState(false);
// vs
// object
const { val: state1, set: setState1 } = React.useState(0);
const { val: state2, set: setState2 } = React.useState(false);
Согласитесь вариант с таплом намного удобнее и читабельнее объекта. Но повторюсь, тапл хорош только для нескольких значений, желательно до 3. Иначе будет каша с кучей данных, разных типов, без названий. Держать такое в голове, также как и сапортать, оч сложно.
Вернемся к TS, для определения тапла, типы элементов должны быть между
[]
скобок, таким образом мы явно говорим TSу, что элемент под таким-то индексом имеет такой-то тип. В массивах же тип задается всему массиву:
// tupples
type SimpleTuple = [string, boolean];
type NamedTuple = [isOpened: boolean, toggleOpened: VoidFunction];
type NamedLongTuple = [a: string, b: number, c: VoidFunction, d: boolean | undefined, e: unknown]; // BAD, better to use object
type TailTuple = [...string[], number];
type DestructTuple = [head: number, ...body: string[], tail: number];
// arrays
type SimpleArray = string[];
type UnionArray = Array<string | boolean>; // (string | boolean)[]
Как вы заметили, таплы в TS могут иметь неопределённую длину, а также типы элементов можно задавать с конца. Кстати,
VoidFunction
это алиас на () => void
который идет по дефолту в dom
неймспейсе, те кто пишет под брузер можете юзать :). Теперь давайте разберем на примере разницу между массивом и таплом:
const tuple: SimpleTuple = ['NAME', false];
const array: UnionArray = ['NAME', false];
const untyped = ['NAME', false];
// works fine, since TS knows that first item is a string!
tuple[0].toLowerCase();
// error: Property 'toLowerCase' does not exist on type 'string | boolean'
// since first element can be string or boolean
array[0].toLowerCase();
// same error as above
untyped[0].toLowerCase();
array[0].toLowerCase();
и untyped[0].toLowerCase();
выдадут ошибки, потому что для array
мы явно указали тип всех элементов массива как string | boolean
, а untyped
так-же получил тип Array<string | boolean>
т.к. всем массивам присвоенным в переменную без явно указанного типа TS дает тип всех его элементов, т.е. в нашем случае будет юнион string | boolean
. Но есть синтаксис как подсказать TS'у что untyped
это тапл, а не массив:
const untyped = ['NAME', false] as const;
untyped[0].toLowerCase(); // works fine
Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3kxH8aQ
#typescript #js
🎃 react-redux и connect-хелл
На днях разговаривая с коллегами и друзьями осознал что далеко не все понимают как работают
Так вот,
Во первых - в достаточно больших проектах на одной странице может быть несколько тысяч компонентов которые используют коннект/селект хук, и все селекторы внутри этих компонентов будут вызываться при каждом изменении стора. Да, с использованием
Во вторых -
Все это может казаться незначительным, но в больших проектах такие мелочи вытекают в реальные проблемы и приходится придумывать какие-то решение, а иногда и хаки, т.к. съехать с редакса не так-то просто 😢.
Одно из простых и довольно быстрых решений, которое так-же решает проблемы с проп-дрилингом - использовать контекст для всего, что меняется редко, но юзается часто. Я не говорю хранить значение только в контексте, можно использовать контекст как механизм доставки данных из редакса в глубь дерева. Например цветовая тема, или локаль браузера, или текущий юзер, все эти данные меняются очень редко, но могут использоваться сотнями компонентов. Если тянуть эти данные из редакса прямо в компонентах - будет connect-hell. Но можно создать отдельные контексты для темы, локали, и т.д., обернуть апку в эти контексты, пробросить им данные из редакса, а в компонентах юзать только контекст. Таким образом вместо сотен/тысяч конектов мы имеем только 1, там где данные тянуться из редакса и пробрасываются в контекст, а компоненты будут обновляться автоматически при изменении значений в контексте самим реактом.
Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3zdmFgL
#react #redux
На днях разговаривая с коллегами и друзьями осознал что далеко не все понимают как работают
connect
и useSelector
и почему даже с реселектом перфоманс может просесть.Так вот,
mapStateToProps
из connect
и селектор из useSelector
вызываются при каждом изменении вашего стора, вообще при каждом изменении, без исключений. Если вы используете connect
или useSelector
только в рутовых компонентах (обычно самые верхние компоненты в дереве, например компоненты роутов) или модулях (компоненты который конектятся к стору но используется на странице всего несколько раз), то скорее всего у вас все ОК. Да будет гемор с проп-дрилингом (прокидывание пропсов в глубь дерева), но об этом ниже. Ну а если вы юзаете connect
или useSelector
везде где только можно, то могут быть проблемы с перфом. Во первых - в достаточно больших проектах на одной странице может быть несколько тысяч компонентов которые используют коннект/селект хук, и все селекторы внутри этих компонентов будут вызываться при каждом изменении стора. Да, с использованием
memo
или connect
, все компоненты перерисовываться не будут, только те, для которых данные реально поменялись, но это делает реакт, а не redux
или reselct
. Представьте что у вас реалтайм приложение, экшены приходят по сокетам, несколько(а может 10+) раз в секунду, так вот на каждый такой экшен все видимые селекторы (которые используется в отрендереных комонентах) будут запущены. А если реалтайм это не про меня, возразите вы, то вот пример с когда-то популярной либой redux-form
, где на каждое нажатие клавише при вводе в инпут летит экшен в стор и опять эти тысячи селекторов будут запущены, на каждое нажатие клавиши, НА КАЖДОЕ КАРЛ...Во вторых -
connect
, в отличает от useSelector
, гарантирует что у родительских компонентов mapStateToProps
будет вызван раньше чем у дочерних, это нужно что бы пофиксить "stale props" и "Zombie child" ([https://react-redux.js.org/api/hooks#stale-props-and-zombie-children](https://react-redux.js.org/api/hooks#stale-props-and-zombie-children)). Эта гарантия добавляет экстра логику и кучу вложенных провайдеров, что так-же влияет на перф при ооочень большой вложенности конектов. В добавок connect
под капотом юзает PureComponnet
и оптимизирует ререндеры, но во многих ситуациях это излишне. Например все с тем-же redux-form
- зачем нам сравнивать пропсы, если мы и так знаем что компонент будет перерисован при каждом нажатии клавиши(читай изменении пропса).Все это может казаться незначительным, но в больших проектах такие мелочи вытекают в реальные проблемы и приходится придумывать какие-то решение, а иногда и хаки, т.к. съехать с редакса не так-то просто 😢.
Одно из простых и довольно быстрых решений, которое так-же решает проблемы с проп-дрилингом - использовать контекст для всего, что меняется редко, но юзается часто. Я не говорю хранить значение только в контексте, можно использовать контекст как механизм доставки данных из редакса в глубь дерева. Например цветовая тема, или локаль браузера, или текущий юзер, все эти данные меняются очень редко, но могут использоваться сотнями компонентов. Если тянуть эти данные из редакса прямо в компонентах - будет connect-hell. Но можно создать отдельные контексты для темы, локали, и т.д., обернуть апку в эти контексты, пробросить им данные из редакса, а в компонентах юзать только контекст. Таким образом вместо сотен/тысяч конектов мы имеем только 1, там где данные тянуться из редакса и пробрасываются в контекст, а компоненты будут обновляться автоматически при изменении значений в контексте самим реактом.
Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3zdmFgL
#react #redux
🗽vite - будущее уже здесь
Сегодня знаменательный день, мы переехали с webpack5 на vite. Знаменательный он потому, что локальная разработка стала такой-же быстрой, как лет 8 назад, когда я делал простенькие сайтики на чистом html, css и немного js, а галповского лайврелоада хватало с головой.
Для тех кто не в курсе, Vite - это очередной бандлер, но бандлер следующего поколения и сервер в одной туле. Он очень и очень шустрый, как сказал мой коллега - "tested, can confirm, fast as fuck", и супер простой в конфигурации. Если сравнивать webpack и vite, то это как жигуль и тесла роадстер на автопилоте, на тесле - сел и поехал с кайфом, а жигуль сколько не переберай, сильно быстрее он от этого не станет.
Vite настолько быстрый потому, что под капотом использует несколько бандлеров. Во время локальной разработки для JS/TS используется esbuild, для всего остального используется rollup. Для прод билда используется только rollup и terser(для минификации), но начиная с версии 2.6.0 esbuild заменит terser. Как вы уже могли догадаться, вся магия скорости кроется в esbuild, т.к. этот js/ts бандлер написан на go и дает 10-100 кратный прирост скорости по сравнению с другими бандлерами, и в 20-40 раз быстрее терсера (правда компрессия хуже на 1-2%, но это не так критично, не так ли?)! Но дело не только в этом, Hot Module Replacement(HMR) так же безумно шустрый, так как vite использует последний JS и натинвые ES-модули, т.е. esbuild просто транспалит ваш TS в JS и все, а дальше vite грузит все файлы в браузер по http2 и когда что-то меняется в файле, то транспайлтся, грузится и заменяется только этот файл. В то время как остальные бандлеры в браузер грузиться уже сбилженые бандлы, что значительно все замедляет и усложняет HMR...
https://deveasy.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F03697b91-719a-4b4b-a312-bc0525721b01%2FUntitled.png?table=block&id=dd187f65-7e43-45f1-a967-25eeb62f2628
Процесс миграции в первом коменте...
Читать в ноушен: https://bit.ly/3lTicvd
#vite #frontend #tools
Сегодня знаменательный день, мы переехали с webpack5 на vite. Знаменательный он потому, что локальная разработка стала такой-же быстрой, как лет 8 назад, когда я делал простенькие сайтики на чистом html, css и немного js, а галповского лайврелоада хватало с головой.
Для тех кто не в курсе, Vite - это очередной бандлер, но бандлер следующего поколения и сервер в одной туле. Он очень и очень шустрый, как сказал мой коллега - "tested, can confirm, fast as fuck", и супер простой в конфигурации. Если сравнивать webpack и vite, то это как жигуль и тесла роадстер на автопилоте, на тесле - сел и поехал с кайфом, а жигуль сколько не переберай, сильно быстрее он от этого не станет.
Vite настолько быстрый потому, что под капотом использует несколько бандлеров. Во время локальной разработки для JS/TS используется esbuild, для всего остального используется rollup. Для прод билда используется только rollup и terser(для минификации), но начиная с версии 2.6.0 esbuild заменит terser. Как вы уже могли догадаться, вся магия скорости кроется в esbuild, т.к. этот js/ts бандлер написан на go и дает 10-100 кратный прирост скорости по сравнению с другими бандлерами, и в 20-40 раз быстрее терсера (правда компрессия хуже на 1-2%, но это не так критично, не так ли?)! Но дело не только в этом, Hot Module Replacement(HMR) так же безумно шустрый, так как vite использует последний JS и натинвые ES-модули, т.е. esbuild просто транспалит ваш TS в JS и все, а дальше vite грузит все файлы в браузер по http2 и когда что-то меняется в файле, то транспайлтся, грузится и заменяется только этот файл. В то время как остальные бандлеры в браузер грузиться уже сбилженые бандлы, что значительно все замедляет и усложняет HMR...
https://deveasy.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F03697b91-719a-4b4b-a312-bc0525721b01%2FUntitled.png?table=block&id=dd187f65-7e43-45f1-a967-25eeb62f2628
Процесс миграции в первом коменте...
Читать в ноушен: https://bit.ly/3lTicvd
#vite #frontend #tools
🔫 самое коварное CSS свойство
Пару недель назад наш дизайнер завел ишью, мол у него в хроме часть нашей апки, а именно канвас, как-то странно рендерится. В нашем понимании канвас - это полотно, где можно драгать всякие элементы(блоки, картинки, текст), зумить и тд, создавать своего рода диаграммы, но с технической стороны это HTML+SVG (для конектов между блоками). Так вот при минимальном зуме, когда весь контент виден сразу, этот самый канвас рендерился криво, то стрелки пропадали, то картинки не полностью рисовались, то блоки при ховере блинкали, то еще что-то странное. Естественно в большинстве случаев все вместе взятое. Я начал дебажить, но у меня, моих коллег и у остальных продуктов все было норм, на всякий случай я перепроверил коммиты, что бы убедится что за последнее время не было никаких фиксов/фич которые могли поломать канвас. Ничего не найдя - откомментил что не воспроизводится, закрыл таску и отписал дизайнеру взять помощнее комп, а то у него там скетч и фигма на пару сотен бордов сжирают все ресурсы 😂
И вот вчера опять прилетает мне эта ишью, но уже от интерпрайз клиентов, продукты в панике, мол аффектает топовых юзиков. Пробую воспроизвести - и опа, у меня тоже воспроизводится. Еще раз смотрю коммиты - ничего что связанно с канвасом за последние пару месяцев, странно, очень странно... Сажусь за дебаг, вроде все работает, дом-ноды рисуются на обычном зуме, никакого JS кода который как-то там кропает элементы у нас нет. Начинаю осознавать, что скорее всего что-то замедляет рендеринг хрома. Пробую замерить память, на большом проекте дев тулз тупо падает и перезагружает страницу. Иду на перформанс табку и делаю замеры. Тут, с первого взгляда, все норм, нет экспенсив js, большенство показателей не особо отличаются от такого же замера на маленьком проекте. НО обращаю внимания что рендер таска какая-то уж слишком большая и composite layers фаза находится сильно далеко от последнего paintа, который на удивление очень маленький. И тут до меня доходит - слои... Открыв вкладку слоев и подождав около минуты, пока она прогрузится, увидел +100500 слоев. Благо при клике на слой, хром выдает инфу, для какой дом-ноды этот слой и почему он был создан, иначе я не представляю как такое можно дебажить...
Как оказалось, для каждого элемента на канвасе создавался новый слой. Но не потому что на этом элементе есть
Вывод: используйте
Читать в ноушен: https://bit.ly/3aeg8si
#css #debug #performance
Пару недель назад наш дизайнер завел ишью, мол у него в хроме часть нашей апки, а именно канвас, как-то странно рендерится. В нашем понимании канвас - это полотно, где можно драгать всякие элементы(блоки, картинки, текст), зумить и тд, создавать своего рода диаграммы, но с технической стороны это HTML+SVG (для конектов между блоками). Так вот при минимальном зуме, когда весь контент виден сразу, этот самый канвас рендерился криво, то стрелки пропадали, то картинки не полностью рисовались, то блоки при ховере блинкали, то еще что-то странное. Естественно в большинстве случаев все вместе взятое. Я начал дебажить, но у меня, моих коллег и у остальных продуктов все было норм, на всякий случай я перепроверил коммиты, что бы убедится что за последнее время не было никаких фиксов/фич которые могли поломать канвас. Ничего не найдя - откомментил что не воспроизводится, закрыл таску и отписал дизайнеру взять помощнее комп, а то у него там скетч и фигма на пару сотен бордов сжирают все ресурсы 😂
И вот вчера опять прилетает мне эта ишью, но уже от интерпрайз клиентов, продукты в панике, мол аффектает топовых юзиков. Пробую воспроизвести - и опа, у меня тоже воспроизводится. Еще раз смотрю коммиты - ничего что связанно с канвасом за последние пару месяцев, странно, очень странно... Сажусь за дебаг, вроде все работает, дом-ноды рисуются на обычном зуме, никакого JS кода который как-то там кропает элементы у нас нет. Начинаю осознавать, что скорее всего что-то замедляет рендеринг хрома. Пробую замерить память, на большом проекте дев тулз тупо падает и перезагружает страницу. Иду на перформанс табку и делаю замеры. Тут, с первого взгляда, все норм, нет экспенсив js, большенство показателей не особо отличаются от такого же замера на маленьком проекте. НО обращаю внимания что рендер таска какая-то уж слишком большая и composite layers фаза находится сильно далеко от последнего paintа, который на удивление очень маленький. И тут до меня доходит - слои... Открыв вкладку слоев и подождав около минуты, пока она прогрузится, увидел +100500 слоев. Благо при клике на слой, хром выдает инфу, для какой дом-ноды этот слой и почему он был создан, иначе я не представляю как такое можно дебажить...
Как оказалось, для каждого элемента на канвасе создавался новый слой. Но не потому что на этом элементе есть
will-change
, translate3d
или еще какой-то хак для создания слоя, про это мы знаем и стараемся не перегружать хром. А потому что у SVGшки, которая рисует конекты для блоков, был задан will-change
и эта SVGшка лежит под всеми дом-нодами на канвсе. И что бы хром мог это все нормально скомпоновать, ему нужно все дом-ноды с z-indexом выше чем у этой SVGшки вынести на отдельный слой. ТАДА - у нас потекли слои, чем больше блоков на канвасе, тем больше пидалит хром. Фикс изи - просто убрали will-change
с SVG, плюс сделали небольшую оптимизацию - теперь добавляем will-change
дом-нодам которые драгаются на данные момент, и убираем после завершения операции. Таким образом дом-ноды которые драгаются выносятся на новый слой, и отрисовка для них происходит быстрее.Вывод: используйте
will-change
(и дургие хаки для создания слоев) с умом, добавляйте до начала операции/анимации и убирайте по завершению. Не добавляйте его просто так, на статик элементы, которые когда-нибудь могут быть проанимированны, ибо на слабых девайсах, или с обновлением хром(как было у нас) все будет тупить...Читать в ноушен: https://bit.ly/3aeg8si
#css #debug #performance
🚐 лайфхаки ревью
В стартапе где я работаю нет тестировщиков, от слова вообще. Мы, конечно же, сами тестим наш код, а еще код тестируется другим разработчиком на этапе ревью, но будем честны, разработчики не очень любят, хотят, могут, умеют, +100500 других отмазок, заниматься этим делом, нам бы кода побольше написать. Поэтому качество этих проверок особого доверия не вызывает... Все вышесказанное означает что код-ревью, это один из основных механизмов для выявления багов до того, как они попали на прод, и к нему нужно подходить ответственно и проверять не только на код-стайл и сомнительные техники, а также понимать что и для чего было изменено, как изменения затронут остальные части системы, и т.д. Все усугубляется тем, что у нас стартап и разработка ведется очень активно, стабильно пару раз в месяц прилетаю PRы на тысячи(а то и десятки тысяч) изменений и сотни файлов (из последнего что смотрел: 524 файла, ~12,5K изменений). Проводить ревью такого PR очень сложно, нужно держать в голове много информации и то, как все изменения связанны между собой. Поэтому для себя вывел несколько техник для упрощения процесса ревью:
- в огромных PRах часть изменений вообще никак не связаны с фичей: рефакторинг(js в ts, перевод класс компонента на функциональный, и тд), новые утилиты, обновление/замена пакетов, новые компоненты или их состояния(новый вариант кнопки, поддержка экспорта в пдф). Все это можно и нужно разбивать на более мелкие PRы, даже если какая-то часть пока-что не будет использоваться(добавьте TODO с ссылкой на PR где это юзается). Это уменьшит дифф, а значит и сложность ревью основного PR и позволит сосредоточится только на изменениях в системе.
- исходя из пункта выше, первым делом смотрю на конфиги, утилиты, обновление/добавление либ(ченджлог, апи, что делает либа), как изменились шаред компоненты системы(та же кнопка или сервис экспорта). В дальнейшем это позволит уменьшить количество прыжков между файлами и сделает ревью быстрее.
- следующим шагом проверяю самый верхний компонент системы который был изменен (компонент страницы, какой-то контекст, роутер, контроллер, и тд) и иду вглубь изменений дерева файлов. В таком случае проще понять основную идею PRа, и, продвигаясь вглубь, разобраться в деталях реализации. Если делать наоборот, то иногда не совсем понятно почему тот или иной компонент (где-то внутри системы) был изменен именно таким образом и придется искать как он используется, а это значит переключение фокуса и потеря контекста.
- бывают такие случаи, когда изменения, это просто перенос кода из одного файла в другой, при этом исходный файл не удаляется. В таких случаях идеально подходят тулы для нахождения диффов в тексте, просто скармливаете "до" и "после", если диффов нет - то и в коде разбираться не нужно. Кстати эта фича есть в VS Code.
- иногда Github не может подсветить нормально изменения или алгоритм нахождения диффов ломается. В таких случаях я пользуюсь GitKraken(GUI клиент к гиту, к сожалению он платный) - у него диффы отображаются более корректно и наглядно, но для этого код нужно склонить. В целом это же можно сделать и в VS Code.
- мы также практикуем следующее: человек который создает PR, сам же его просматривает и оставляет комментарии в каких-то неочевидных местах. Зачем была обновлена/заменена либа, почему был создан такой-то метод, если есть похожий и тд. Благодаря таким комментам ревьюить огромные PR намного проще.
- а еще у меня есть парочка хром экстеншенов которые прям очень помогают в ревью да и в целом улучшают взаимодействие с гитхабом
Экстеншены в первом коменте...
Читать в ноушен: https://bit.ly/3p69ehk
#review #pr
В стартапе где я работаю нет тестировщиков, от слова вообще. Мы, конечно же, сами тестим наш код, а еще код тестируется другим разработчиком на этапе ревью, но будем честны, разработчики не очень любят, хотят, могут, умеют, +100500 других отмазок, заниматься этим делом, нам бы кода побольше написать. Поэтому качество этих проверок особого доверия не вызывает... Все вышесказанное означает что код-ревью, это один из основных механизмов для выявления багов до того, как они попали на прод, и к нему нужно подходить ответственно и проверять не только на код-стайл и сомнительные техники, а также понимать что и для чего было изменено, как изменения затронут остальные части системы, и т.д. Все усугубляется тем, что у нас стартап и разработка ведется очень активно, стабильно пару раз в месяц прилетаю PRы на тысячи(а то и десятки тысяч) изменений и сотни файлов (из последнего что смотрел: 524 файла, ~12,5K изменений). Проводить ревью такого PR очень сложно, нужно держать в голове много информации и то, как все изменения связанны между собой. Поэтому для себя вывел несколько техник для упрощения процесса ревью:
- в огромных PRах часть изменений вообще никак не связаны с фичей: рефакторинг(js в ts, перевод класс компонента на функциональный, и тд), новые утилиты, обновление/замена пакетов, новые компоненты или их состояния(новый вариант кнопки, поддержка экспорта в пдф). Все это можно и нужно разбивать на более мелкие PRы, даже если какая-то часть пока-что не будет использоваться(добавьте TODO с ссылкой на PR где это юзается). Это уменьшит дифф, а значит и сложность ревью основного PR и позволит сосредоточится только на изменениях в системе.
- исходя из пункта выше, первым делом смотрю на конфиги, утилиты, обновление/добавление либ(ченджлог, апи, что делает либа), как изменились шаред компоненты системы(та же кнопка или сервис экспорта). В дальнейшем это позволит уменьшить количество прыжков между файлами и сделает ревью быстрее.
- следующим шагом проверяю самый верхний компонент системы который был изменен (компонент страницы, какой-то контекст, роутер, контроллер, и тд) и иду вглубь изменений дерева файлов. В таком случае проще понять основную идею PRа, и, продвигаясь вглубь, разобраться в деталях реализации. Если делать наоборот, то иногда не совсем понятно почему тот или иной компонент (где-то внутри системы) был изменен именно таким образом и придется искать как он используется, а это значит переключение фокуса и потеря контекста.
- бывают такие случаи, когда изменения, это просто перенос кода из одного файла в другой, при этом исходный файл не удаляется. В таких случаях идеально подходят тулы для нахождения диффов в тексте, просто скармливаете "до" и "после", если диффов нет - то и в коде разбираться не нужно. Кстати эта фича есть в VS Code.
- иногда Github не может подсветить нормально изменения или алгоритм нахождения диффов ломается. В таких случаях я пользуюсь GitKraken(GUI клиент к гиту, к сожалению он платный) - у него диффы отображаются более корректно и наглядно, но для этого код нужно склонить. В целом это же можно сделать и в VS Code.
- мы также практикуем следующее: человек который создает PR, сам же его просматривает и оставляет комментарии в каких-то неочевидных местах. Зачем была обновлена/заменена либа, почему был создан такой-то метод, если есть похожий и тд. Благодаря таким комментам ревьюить огромные PR намного проще.
- а еще у меня есть парочка хром экстеншенов которые прям очень помогают в ревью да и в целом улучшают взаимодействие с гитхабом
Экстеншены в первом коменте...
Читать в ноушен: https://bit.ly/3p69ehk
#review #pr
⚛️ неочевидный react
Похоже, далеко не все понимают как работает React, в частности: контекст, рендер, как всплывают события и почему происходят ремаунты, казалось бы в тех местах, где их не должно быть. Я конечно не претендую на реакт-эксперта, но хотел бы прояснить кое-какие моменты, которые не описаны в доке, или упоминаются как-то вскользь. Будет много ссылок на демки где можно поиграться, посмотреть логи и подробнее разобраться.
- Начнем с самого простого - контекста. Контекст нужен что бы доставить данные вглубь React дерева, при это избегая props-drillingа, пробрасывания данных через кучу родителей к дочерним. В целом в доке все отлично расписано, но многие не обращают внимание на один важный момент: все потребители контекста, неважно, это
https://codesandbox.io/s/object-in-the-context-nh1ud?file=/src/App.tsx в этом примере на каждый рендер провайдера создается новый объект, что вызывает перерасчет всех потребителей, в больших приложениях это может привести к большому даунгрейду перфа. Фикситься достаточно просто, использованием
- Как я понял, многие предполагают что компонент монтируются (или по крайней мере его рендер вызывается) в момент создание элемента
https://codesandbox.io/s/react-render-8zm5d?file=/src/App.tsx:1059-1070 в примере выше
1) компонент использует
2) новый элемент не был создан в момент ререндера родителя - это можно сделать различными способами, в примере по ссылке это делается через создание элементов за пределами рендер метода. Но того же эффекта можно добиться положив элемент в
Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3jwrFYY
#react
Похоже, далеко не все понимают как работает React, в частности: контекст, рендер, как всплывают события и почему происходят ремаунты, казалось бы в тех местах, где их не должно быть. Я конечно не претендую на реакт-эксперта, но хотел бы прояснить кое-какие моменты, которые не описаны в доке, или упоминаются как-то вскользь. Будет много ссылок на демки где можно поиграться, посмотреть логи и подробнее разобраться.
- Начнем с самого простого - контекста. Контекст нужен что бы доставить данные вглубь React дерева, при это избегая props-drillingа, пробрасывания данных через кучу родителей к дочерним. В целом в доке все отлично расписано, но многие не обращают внимание на один важный момент: все потребители контекста, неважно, это
Context.Consumer
или React.useContext(Context)
, будут перерисовываться (т.е. вызван render метод, а не обновлен DOM) каждый раз когда значение контекста меняется. Это значит что использовать объекты, без предварительного кэширования, в контексте - плохая идея, т.к. все потребители будут перерисовываться на каждый рендер провайдера, даже если данные в этом объекте никогда не меняются.
<Context.Provider value={{ value: 20 }}>{children}</Context.Provider>
https://codesandbox.io/s/object-in-the-context-nh1ud?file=/src/App.tsx в этом примере на каждый рендер провайдера создается новый объект, что вызывает перерасчет всех потребителей, в больших приложениях это может привести к большому даунгрейду перфа. Фикситься достаточно просто, использованием
useMemo
или useRef
, в зависимости от того, меняются ли значения в объекте.- Как я понял, многие предполагают что компонент монтируются (или по крайней мере его рендер вызывается) в момент создание элемента
const child = <Child />
, даже если элемент не был использован в ретурне рендера родителя. На самом деле это не так, компонент не монтируется и даже его рендер не вызывается, если элемента этого компонента нет в React дереве, другими словами если компонент не был использован в ретурне рендера родителя. Отсюда вытекает еще одна неочевидность: компонент монтируются столько раз, сколько раз его элемент появился в React дереве.
const child = <Child name="renderChild" />;
const unusedChild = <Child name="unusedChild" />;
return (
<>
{child}
{child}
{child}
{false && unusedChild}
</>
)
https://codesandbox.io/s/react-render-8zm5d?file=/src/App.tsx:1059-1070 в примере выше
Child
с названием renderChild монтируются 3 раза, т.к. 3 элемента используется в дереве, а вот unusedChild вообще не монтируются да и рендер для него тоже не вызовется, поскольку его нет в дереве. Все тоже касается и последующих ререндеров, но здесь есть 2 НО, когда ререндера может не произойти: 1) компонент использует
React.iss.onemo
- пропсы не поменялись, ререндер не произошел2) новый элемент не был создан в момент ререндера родителя - это можно сделать различными способами, в примере по ссылке это делается через создание элементов за пределами рендер метода. Но того же эффекта можно добиться положив элемент в
useRef
или useMemo
. Чет мне кажется, что работает оно так-же как и React.iss.onemo
только без дополнительных проверок пропсов в момент рендера, а просто сравнивается сслыка элемента - я не проверял в сорцах, просто предположение.Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3jwrFYY
#react
👍1
🦾 local env на стиройдах
Как я уже упоминал несколько раз, весь наш бэкенд построен на микросервисах. Есть сервисы-апишки - общаются с фронтом, сервисы-рантаймы - ранают скилы/экшены/проекты и интегрируются с alexa/google assistant/voiceflow(наш кастомный ассистент)/прочими платформами, ну и куча других сервисов с джобами, nlp/nlu, доступа к данным и тд. На каждую платформу приходится как минимум по 2 сервиса (апишка и рантайм), следовательно кол-во сервисов растет с добавлением новых платформ в проект. Все это работает прекрасно и позволяет нам скейлится по мере необходимости и расширять платформы не боясь, что интеграция с существующей платформой отвалится.
Но во всей этой красоте есть один большой нюанс - local env.
- Во-первых, засетапить такой зоопарк локально достаточно сложно:
- 3 базы данных
- куча сервисов
- практически все сервисы общаются с сервисами доступа к данным и кэшем
- сервисы апишки/рантаймы для платформ (alexa/google/...) общаются с апишкой/рантаймом voiceflow (что бы конвертнуть проект платформы в проект voiceflow или запустить проект у нас в туле, без коммуникации с платформой)
- фронт общается с апишками-платформ, дженерик апишкой, ну и парочку других сервисов
Другими словами сервисы и фронт между собой связанны, и в env-переменных нужно ссылаться друг на друга. Новые члены команды обычно страдали первые пару дней, что бы все это запустить...
- Во вторых, наш фронт сам по себе достаточно тяжелый и dev-server хавает много ресурсов. А если запустить весь этот зоопарк сервисов, то macbook с 16 гигами не справляется и разрабатывать практически невозможно, и не важно что сервисы на ноде и сами по себе не ресурсоёмкие, но их много...
- Ну и в третьих, не совсем относится к local env, но развернуть тестовый env в облаке(где одни сервисы юзают код из своих мастер веток, а другие - из кастомных) - геморно и требует совместной работы с девопсами. Поэтому мы юзали костыль в виде нескольких staging (staging-a, staging-b, etc) веток в каждой репе, и пушили туда свои ветки и проверяли в облаке 😂.
Около годна назад наша инфра-тима решила помочь нам с этим и написала cli-тулу, которая решала первую и третью проблемы, а так же частично вторую. Эта тула может одной командой склонить все наши репы(сервисы, фронт, базы, либы). Создать локальные env файлы и проставить нужные env-переменные, что бы залинковать сервисы и быза. Ну и естественно запустить весь этот зоопарк локально. Есть и набор снипетов, что бы ранать только часть сервисов, например только те сервисы которые необходимы для работы фронта, что позволяло экономить ресурсы. Тула также позволяет одной командой создать env в облаке, все сервисы разворачиваются и ты получаешь урл на новый env. По дефолту сервисы юзают мастер ветки, но есть команда, что бы переключить любой сервис на нужную ветку. Под капотом тула написана на Go, юзает докер и кубик.
В целом на этом можно было и закончить, но проблема с ресурсами не была решена для всех. Некоторым ребятам в команде (в основном бэкендерам) нужно ранать все сервисы, ибо большинство новых фич затрагивают все платформы. Да и к тому же сервисов становится все больше, докер хавает все больше, комп греется все больше, кернел тормозит процесс все больше, в итоге через пару часов активной разработки летом я вырубал мак и клал его в морозилку, что бы усмирить кернел таск, как вам лайфхак?
Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3Dq9XhX
#tools #longread
Как я уже упоминал несколько раз, весь наш бэкенд построен на микросервисах. Есть сервисы-апишки - общаются с фронтом, сервисы-рантаймы - ранают скилы/экшены/проекты и интегрируются с alexa/google assistant/voiceflow(наш кастомный ассистент)/прочими платформами, ну и куча других сервисов с джобами, nlp/nlu, доступа к данным и тд. На каждую платформу приходится как минимум по 2 сервиса (апишка и рантайм), следовательно кол-во сервисов растет с добавлением новых платформ в проект. Все это работает прекрасно и позволяет нам скейлится по мере необходимости и расширять платформы не боясь, что интеграция с существующей платформой отвалится.
Но во всей этой красоте есть один большой нюанс - local env.
- Во-первых, засетапить такой зоопарк локально достаточно сложно:
- 3 базы данных
- куча сервисов
- практически все сервисы общаются с сервисами доступа к данным и кэшем
- сервисы апишки/рантаймы для платформ (alexa/google/...) общаются с апишкой/рантаймом voiceflow (что бы конвертнуть проект платформы в проект voiceflow или запустить проект у нас в туле, без коммуникации с платформой)
- фронт общается с апишками-платформ, дженерик апишкой, ну и парочку других сервисов
Другими словами сервисы и фронт между собой связанны, и в env-переменных нужно ссылаться друг на друга. Новые члены команды обычно страдали первые пару дней, что бы все это запустить...
- Во вторых, наш фронт сам по себе достаточно тяжелый и dev-server хавает много ресурсов. А если запустить весь этот зоопарк сервисов, то macbook с 16 гигами не справляется и разрабатывать практически невозможно, и не важно что сервисы на ноде и сами по себе не ресурсоёмкие, но их много...
- Ну и в третьих, не совсем относится к local env, но развернуть тестовый env в облаке(где одни сервисы юзают код из своих мастер веток, а другие - из кастомных) - геморно и требует совместной работы с девопсами. Поэтому мы юзали костыль в виде нескольких staging (staging-a, staging-b, etc) веток в каждой репе, и пушили туда свои ветки и проверяли в облаке 😂.
Около годна назад наша инфра-тима решила помочь нам с этим и написала cli-тулу, которая решала первую и третью проблемы, а так же частично вторую. Эта тула может одной командой склонить все наши репы(сервисы, фронт, базы, либы). Создать локальные env файлы и проставить нужные env-переменные, что бы залинковать сервисы и быза. Ну и естественно запустить весь этот зоопарк локально. Есть и набор снипетов, что бы ранать только часть сервисов, например только те сервисы которые необходимы для работы фронта, что позволяло экономить ресурсы. Тула также позволяет одной командой создать env в облаке, все сервисы разворачиваются и ты получаешь урл на новый env. По дефолту сервисы юзают мастер ветки, но есть команда, что бы переключить любой сервис на нужную ветку. Под капотом тула написана на Go, юзает докер и кубик.
В целом на этом можно было и закончить, но проблема с ресурсами не была решена для всех. Некоторым ребятам в команде (в основном бэкендерам) нужно ранать все сервисы, ибо большинство новых фич затрагивают все платформы. Да и к тому же сервисов становится все больше, докер хавает все больше, комп греется все больше, кернел тормозит процесс все больше, в итоге через пару часов активной разработки летом я вырубал мак и клал его в морозилку, что бы усмирить кернел таск, как вам лайфхак?
Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3Dq9XhX
#tools #longread