Насколько React “жирный”?
Без лишней лирики, сразу к цифрам: пустое приложение на React (для веба) обойдётся вам в 187 kB пожатого js, и это только React, без каких либо дополнительных библиотек. Причём примерно 94% это react-dom-client.
Напомню, что количество js кода, это не только про скорость загрузки, но и про время парсинга, и в целом про то, как быстро будет рисоваться ваш UI.
А теперь сравним с пустым приложением на SolidJS: всего 6 kB!
Во-первых: SolidJS сам по себе компактный.
Во-вторых: он поддерживает tree-shaking (чего не может React).
Или вот реальный проект на SolidJS: 7 зависимостей (роутер и т.п.), 16 страниц, куча UI компонентов, таблицы, формы, списки и т.д., всего 702 модуля. И вот это всё весит 270 kB (233 kB без keycloak).
П.С. Пока писал, вспомнил что уже в 2015 г. были легковесные варианты React. Именно тогда и появился Preact.
Без лишней лирики, сразу к цифрам: пустое приложение на React (для веба) обойдётся вам в 187 kB пожатого js, и это только React, без каких либо дополнительных библиотек. Причём примерно 94% это react-dom-client.
Напомню, что количество js кода, это не только про скорость загрузки, но и про время парсинга, и в целом про то, как быстро будет рисоваться ваш UI.
А теперь сравним с пустым приложением на SolidJS: всего 6 kB!
Во-первых: SolidJS сам по себе компактный.
Во-вторых: он поддерживает tree-shaking (чего не может React).
Или вот реальный проект на SolidJS: 7 зависимостей (роутер и т.п.), 16 страниц, куча UI компонентов, таблицы, формы, списки и т.д., всего 702 модуля. И вот это всё весит 270 kB (233 kB без keycloak).
П.С. Пока писал, вспомнил что уже в 2015 г. были легковесные варианты React. Именно тогда и появился Preact.
🔥2
audio/ogg; codecs=opus
Если вам понадобится реализовать запись и/или воспроизведение аудио файлов
В качестве решения подойдёт npm пакет opus-recorder
https://github.com/chris-rudmin/opus-recorder
Также стоит обратить внимание на пакет wasm-audio-decoders
https://github.com/eshaz/wasm-audio-decoders
Если вам понадобится реализовать запись и/или воспроизведение аудио файлов
audio/ogg; codecs=opus, вы можете обнаружить что поддержка этой пары (контейнер + кодек) довольно скромная. Особенно грустно дела обстоят с записью, и если я правильно помню, на это способен только Firefox.В качестве решения подойдёт npm пакет opus-recorder
https://github.com/chris-rudmin/opus-recorder
Также стоит обратить внимание на пакет wasm-audio-decoders
https://github.com/eshaz/wasm-audio-decoders
Деградация кодовой базы
Ни для кого не секрет, что со временем кодовая база, практически любого проекта, так или иначе деградирует. Разрабатывать становится сложнее, задачи делаются дольше, нужно больше рефакторить и т.д. На самом деле, причин деградации кода много, и если пытаться описать их все, то получится не маленькая такая книга, а то и не одна. Так что я лишь слегка затрону этот вопрос, но с довольно важной стороны.
Если максимально упростить, деградация кода — это когда, с течением времени, код становится всё менее понятным и всё более запутанным. Отсюда можно сделать вывод, что если вы просто добавляете новый функционал и расширяете текущий, и не прикладываете дополнительных усилий для улучшения кода в целом, то ваша кодовая база уже деградирует.
На мой взгляд, одна из самых наглядных аналогий — шкаф с вещами. Сначала у вас мало вещей и всё аккуратно разложено по полкам, потом вы что-то достаёте, что-то возвращаете, и постепенно порядок превращается в кучу в которой становится сложно что-то найти. Но и это ещё не всё, время от времени вы добавляете новые вещи, а старые не выкидываете. Страшно подумать во что превратится этот шкаф со временем. 😅
А теперь давайте на это всё накинем то, что этим шкафом пользуются несколько человек одновременно. Если, при этом, нет чёткого разделения на зоны ответственности (зоны владения), то смело можно считать что никто ни за что не отвечает. А к чему приводит безответственность, я думаю, объяснять не надо.
Подытожим:
⁃ Определите зоны ответственности. В кодовой базе не должно быть “серых зон”.
⁃ Прежде чем добавить свой код, разберитесь как работает текущий. Если вам что-то не понятно, найдите автора или старшего разработчика который подскажет.
⁃ Регулярно проводите чистку. Если ваша задача связана с выпиливанием чего-либо, постарайтесь не оставлять “хвостов”.
⁃ Избегайте накопления технического долга, а в идеале следуйте правилу бойскаута: оставь место стоянки чище, чем оно было до тебя.
⁃ Старайтесь писать простой и понятный код, в конце концов 😎
И помните: Чисто не там где убирают, а там где не мусорят.
Ни для кого не секрет, что со временем кодовая база, практически любого проекта, так или иначе деградирует. Разрабатывать становится сложнее, задачи делаются дольше, нужно больше рефакторить и т.д. На самом деле, причин деградации кода много, и если пытаться описать их все, то получится не маленькая такая книга, а то и не одна. Так что я лишь слегка затрону этот вопрос, но с довольно важной стороны.
Если максимально упростить, деградация кода — это когда, с течением времени, код становится всё менее понятным и всё более запутанным. Отсюда можно сделать вывод, что если вы просто добавляете новый функционал и расширяете текущий, и не прикладываете дополнительных усилий для улучшения кода в целом, то ваша кодовая база уже деградирует.
На мой взгляд, одна из самых наглядных аналогий — шкаф с вещами. Сначала у вас мало вещей и всё аккуратно разложено по полкам, потом вы что-то достаёте, что-то возвращаете, и постепенно порядок превращается в кучу в которой становится сложно что-то найти. Но и это ещё не всё, время от времени вы добавляете новые вещи, а старые не выкидываете. Страшно подумать во что превратится этот шкаф со временем. 😅
А теперь давайте на это всё накинем то, что этим шкафом пользуются несколько человек одновременно. Если, при этом, нет чёткого разделения на зоны ответственности (зоны владения), то смело можно считать что никто ни за что не отвечает. А к чему приводит безответственность, я думаю, объяснять не надо.
Подытожим:
⁃ Определите зоны ответственности. В кодовой базе не должно быть “серых зон”.
⁃ Прежде чем добавить свой код, разберитесь как работает текущий. Если вам что-то не понятно, найдите автора или старшего разработчика который подскажет.
⁃ Регулярно проводите чистку. Если ваша задача связана с выпиливанием чего-либо, постарайтесь не оставлять “хвостов”.
⁃ Избегайте накопления технического долга, а в идеале следуйте правилу бойскаута: оставь место стоянки чище, чем оно было до тебя.
⁃ Старайтесь писать простой и понятный код, в конце концов 😎
И помните: Чисто не там где убирают, а там где не мусорят.
🔥4💩1
Не очевидный плюс сторонних пакетов, и что насчёт самописных решений
Обычно я, не то чтобы прям против, но точно не выступаю за использование таких библиотек как Axios. Есть Fetch API, и написать базовую обвязку для него, на уровне проекта, ничего не стоит (правда делать это лучше на старте проекта).
Но сегодня не об этом, а о том что у сторонних решений, помимо очевидных плюсов, есть ещё как минимум один не очевидный — «закрытость» от шаловливых ручек 🙌. К сожалению, любой код лежащий в репозитории проекта практически ни как не застрахован от изменений, в том числе негативных. Чем больше у вас проект и чем больше разработчиков над ним работает, тем выше шанс что кто-то что-то сломает, а архитектор (ну или кто там у вас ответственный, он же у вас есть, да?) даже не увидит этих изменений. И да, не все поломки очевидны и сразу исправляются.
В больших компаниях, общий функционал выносится в отдельные пакеты, в основном, чтобы не дублировать код (не писать велосипеды). Но почему-то мало кто рассматривает этот вариант с точки зрения контроля изменений критической функциональности, ну или как фильтр от поспешных решений по типу «я тут присрал, потому что у меня нет времени, нужно задачки по доске двигать».
Если вы знаете другие варианты защиты от тихих «не санкционированных» изменений, пишите в комментариях.
П.С. Если что, вариант «просто договориться» не работает. 😅
Обычно я, не то чтобы прям против, но точно не выступаю за использование таких библиотек как Axios. Есть Fetch API, и написать базовую обвязку для него, на уровне проекта, ничего не стоит (правда делать это лучше на старте проекта).
Но сегодня не об этом, а о том что у сторонних решений, помимо очевидных плюсов, есть ещё как минимум один не очевидный — «закрытость» от шаловливых ручек 🙌. К сожалению, любой код лежащий в репозитории проекта практически ни как не застрахован от изменений, в том числе негативных. Чем больше у вас проект и чем больше разработчиков над ним работает, тем выше шанс что кто-то что-то сломает, а архитектор (ну или кто там у вас ответственный, он же у вас есть, да?) даже не увидит этих изменений. И да, не все поломки очевидны и сразу исправляются.
В больших компаниях, общий функционал выносится в отдельные пакеты, в основном, чтобы не дублировать код (не писать велосипеды). Но почему-то мало кто рассматривает этот вариант с точки зрения контроля изменений критической функциональности, ну или как фильтр от поспешных решений по типу «я тут присрал, потому что у меня нет времени, нужно задачки по доске двигать».
Если вы знаете другие варианты защиты от тихих «не санкционированных» изменений, пишите в комментариях.
П.С. Если что, вариант «просто договориться» не работает. 😅
❤1✍1👎1
CSS свойство, которое работает только в Safari
Оказывается, и такое бывает. 😅 Safari — единственный (на момент написания этого поста) браузер, который поддерживает hanging-punctuation, аж с 2016 года.
Это свойство отвечает за “висячую пунктуацию”, которая широко известна в кругах профессиональных верстальщиков (тех, что верстают печатную продукцию типа журналов и т.п., а не эти ваши css + html 😎).
Висячая пунктуация — это способ набора и вёрстки текста, при котором знаки препинания, скобки, кавычки, дефисы и маркеры списков, находящиеся в начале или в конце строки, смещаются за границы основного блока текста, то есть "вывешиваются" на поля.
В общем, если типографика для вас не пустой звук, не забывайте что есть такое свойство.
Оказывается, и такое бывает. 😅 Safari — единственный (на момент написания этого поста) браузер, который поддерживает hanging-punctuation, аж с 2016 года.
hanging-punctuation: first allow-end last;
Это свойство отвечает за “висячую пунктуацию”, которая широко известна в кругах профессиональных верстальщиков (тех, что верстают печатную продукцию типа журналов и т.п., а не эти ваши css + html 😎).
Висячая пунктуация — это способ набора и вёрстки текста, при котором знаки препинания, скобки, кавычки, дефисы и маркеры списков, находящиеся в начале или в конце строки, смещаются за границы основного блока текста, то есть "вывешиваются" на поля.
В общем, если типографика для вас не пустой звук, не забывайте что есть такое свойство.
🔥2
Приколы setState в React, и как оно в SolidJS
Сравним один и тот же код изменения состояния в этих двух, похожих, но совершенно разных, библиотеках.
React:
Результат при первом выполнении: 1 2 3 4
При последующих: 1 3 2 4
SolidJS:
Результат всегда одинаковый: 1 2 4 3
А теперь, предлагаю подумать над следующими вопросами, а может и обсудить в комментариях:
- Это ожидаемый результат для вас?
- Почему такая разница в React, между первым и последующими выполнениями?
- Как добиться стабильного результата в React?
- Как добиться результата 1 2 3 4 в обоих случаях?
Сравним один и тот же код изменения состояния в этих двух, похожих, но совершенно разных, библиотеках.
React:
const [state, setState] = useState(() => Date.now());
const onClick = () => {
console.log(1);
setState(() => {
console.log(2);
return Date.now();
});
console.log(3);
};
const computed = useMemo(() => {
console.log(4);
return (new Date(state)).toDateString();
}, [state]);
Результат при первом выполнении: 1 2 3 4
При последующих: 1 3 2 4
SolidJS:
const [state, setState] = createSignal(Date.now());
const onClick = () => {
console.log(1);
setState(() => {
console.log(2);
return Date.now();
});
console.log(3);
};
const computed = createMemo(() => {
console.log(4);
return (new Date(state())).toDateString();
});
Результат всегда одинаковый: 1 2 4 3
А теперь, предлагаю подумать над следующими вопросами, а может и обсудить в комментариях:
- Это ожидаемый результат для вас?
- Почему такая разница в React, между первым и последующими выполнениями?
- Как добиться стабильного результата в React?
- Как добиться результата 1 2 3 4 в обоих случаях?
👍3
Линтеры и форматтеры вредят разработке?
Прочитал тут пару “набросов” от коллег по цеху:
— Как Линтеры и Форматтеры вредят разработке
— Чем проще - тем хуже
Несколько цитат, для тех кому лень переходить по ссылкам:
— «Чем больше автоматизации в написании кода, тем ниже его осознанность»
— «Отсутствие автоформаттера - это инструмент оценки ответственности и отношения к регламентам»
— «линтеры прямо сейчас медленно убивают ваш проект»
— «Упрощение убивает профессию»
Да, к линтерам и форматтерам у меня есть вопросы, как и к любым другим инструментам. И да, в своих проектах стараюсь их не использовать. Но одно дело когда это твой личный проект (или единственный проект компании, где ты единственный разработчик), и совсем другое когда ты работаешь в большой компании с большим количеством разработчиков разного уровня и опыта.
Одно дело ездить по деревне без прав и знания ПДД, и совсем другое — быть участником дорожного движения многомиллионного города. Вы же в курсе, что ППД, буквально, написаны кровью? Надеюсь аналогия ясна.
Может быть я читал жопой, но в обоих постах наблюдаю общую мысль: чем больше думают за нас, тем больше мы тупеем. Возможно в этом есть доля истины, а возможно всё совершенно иначе. Если что-то делает нашу жизнь проще, это возможность сместить свой контекст внимания на что-то более важное. Например, не обращать внимание на форматирование кода, а вместо этого больше думать об архитектуре.
П.С. Ну и да, хуки появились не потому что было сложно с классами.
Прочитал тут пару “набросов” от коллег по цеху:
— Как Линтеры и Форматтеры вредят разработке
— Чем проще - тем хуже
Несколько цитат, для тех кому лень переходить по ссылкам:
— «Чем больше автоматизации в написании кода, тем ниже его осознанность»
— «Отсутствие автоформаттера - это инструмент оценки ответственности и отношения к регламентам»
— «линтеры прямо сейчас медленно убивают ваш проект»
— «Упрощение убивает профессию»
Да, к линтерам и форматтерам у меня есть вопросы, как и к любым другим инструментам. И да, в своих проектах стараюсь их не использовать. Но одно дело когда это твой личный проект (или единственный проект компании, где ты единственный разработчик), и совсем другое когда ты работаешь в большой компании с большим количеством разработчиков разного уровня и опыта.
Одно дело ездить по деревне без прав и знания ПДД, и совсем другое — быть участником дорожного движения многомиллионного города. Вы же в курсе, что ППД, буквально, написаны кровью? Надеюсь аналогия ясна.
Может быть я читал жопой, но в обоих постах наблюдаю общую мысль: чем больше думают за нас, тем больше мы тупеем. Возможно в этом есть доля истины, а возможно всё совершенно иначе. Если что-то делает нашу жизнь проще, это возможность сместить свой контекст внимания на что-то более важное. Например, не обращать внимание на форматирование кода, а вместо этого больше думать об архитектуре.
П.С. Ну и да, хуки появились не потому что было сложно с классами.
👍2
Как проверить формат файла и почему фильтра по accept и type не достаточно
Начнём с самого простого варианта:
Несмотря на вполне конкретные указания, что input может принимать только изображения, операционные системы предоставляют возможность обойти это и выбрать любой файл. Так что не стоит воспринимать параметр accept как полноценный фильтр, по факту это всего лишь подсказка.
Вариант «побогаче» — drag & drop. Тут пользователь вообще ни как не ограничен и нужно программно фильтровать список dataTransfer.items, во-первых, по kind а во-вторых по type (он же MIME тип).
Но даже фильтра по type, на самом деле, может быть не достаточно, т.к. под видом изображения можно загрузить что угодно, например вредоносный код. Помимо безопасности, это вопрос предсказуемости и стабильности системы, а также консистентности данных.
Самое простое, что можно сделать, это проверить сигнатуру файла:
Данные необходимых вам сигнатур можно найти в интернетах, например тут. Остальное — дело техники.
П.С. Где делать подобные проверки, на клиенте или на сервере (а возможно и там и там), зависит от задачи.
Начнём с самого простого варианта:
<input type="file" accept="image/*" />
Несмотря на вполне конкретные указания, что input может принимать только изображения, операционные системы предоставляют возможность обойти это и выбрать любой файл. Так что не стоит воспринимать параметр accept как полноценный фильтр, по факту это всего лишь подсказка.
Вариант «побогаче» — drag & drop. Тут пользователь вообще ни как не ограничен и нужно программно фильтровать список dataTransfer.items, во-первых, по kind а во-вторых по type (он же MIME тип).
Но даже фильтра по type, на самом деле, может быть не достаточно, т.к. под видом изображения можно загрузить что угодно, например вредоносный код. Помимо безопасности, это вопрос предсказуемости и стабильности системы, а также консистентности данных.
Самое простое, что можно сделать, это проверить сигнатуру файла:
function getSignature(buffer: ArrayBuffer, length = 4): string {
return Array.from(new Uint8Array(buffer.slice(0, length)))
.map(num => num.toString(16))
.join("");
}
file.arrayBuffer()
.then(getSignature)
.then(checkSignature);
Данные необходимых вам сигнатур можно найти в интернетах, например тут. Остальное — дело техники.
П.С. Где делать подобные проверки, на клиенте или на сервере (а возможно и там и там), зависит от задачи.
🔥7❤1
useEffectEvent
Похоже проблема массива зависимостей useEffect настолько всех достала, что в React 19.2 включили специальный хук useEffectEvent. Его можно использовать исключительно в паре с useEffect, и к тому же оба хука должны быть объявлены в одной и той же функции (компоненте).
Пример из документации:
А что насчёт эффектов в SolidJS?
— Нет ограничений на расположение (условия не помеха);
— Эффект может быть вложенным, например в другой эффект;
— Нет массива зависимостей (определяется автоматически);
— Для исключения из зависимостей необходимо использовать untrack;
— Эффект не выполнится дважды (привет строгий режим в React);
— Эффект может пропускать первый вызов (реагировать только на изменения);
— Для разового вызова лучше использовать onMount;
— Эффект вообще не нужен, если код нужно выполнить в момент выполнения компонента;
— Эффект может подписываться и отписываться динамически;
— Эффект может получать предыдущее состояние своего выполнения;
— onCleanup можно использовать отдельно;
Похоже проблема массива зависимостей useEffect настолько всех достала, что в React 19.2 включили специальный хук useEffectEvent. Его можно использовать исключительно в паре с useEffect, и к тому же оба хука должны быть объявлены в одной и той же функции (компоненте).
Пример из документации:
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(
serverUrl,
roomId,
);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return ...
}
А что насчёт эффектов в SolidJS?
— Нет ограничений на расположение (условия не помеха);
— Эффект может быть вложенным, например в другой эффект;
— Нет массива зависимостей (определяется автоматически);
— Для исключения из зависимостей необходимо использовать untrack;
— Эффект не выполнится дважды (привет строгий режим в React);
— Эффект может пропускать первый вызов (реагировать только на изменения);
— Для разового вызова лучше использовать onMount;
— Эффект вообще не нужен, если код нужно выполнить в момент выполнения компонента;
— Эффект может подписываться и отписываться динамически;
— Эффект может получать предыдущее состояние своего выполнения;
— onCleanup можно использовать отдельно;
👍3❤1
Раскладывание кода по папкам — архитектура?
Если говорить максимально просто, то архитектура в программировании — это организация системы, которая определяет её основные части и правила, как они между собой взаимодействуют. Например, функциональное мышление — это тоже архитектура, потому что оно делит систему на компоненты (данные, вычисления, действия) и задаёт, как они должны работать вместе.
А что насчёт раскладывания кода по папкам? Да, это тоже часть архитектуры. В FSD (Feature-Sliced Design), например, есть чёткие правила, как разделять код на слои, срезы и сегменты, и как эти части взаимодействуют. В моей версии feature-based архитектуры тоже есть чёткое разделение, но в отличие от FSD, в центре стоит предметная область и функциональное мышление.
Главное — понимать, что архитектура — это очень широкое и абстрактное понятие, и к программированию оно относится не только через код, но и через процессы, структуру команд и даже инфраструктуру. Поэтому раскладывать код по папкам — это один из важных, но не единственных, аспектов архитектуры.
Если говорить максимально просто, то архитектура в программировании — это организация системы, которая определяет её основные части и правила, как они между собой взаимодействуют. Например, функциональное мышление — это тоже архитектура, потому что оно делит систему на компоненты (данные, вычисления, действия) и задаёт, как они должны работать вместе.
А что насчёт раскладывания кода по папкам? Да, это тоже часть архитектуры. В FSD (Feature-Sliced Design), например, есть чёткие правила, как разделять код на слои, срезы и сегменты, и как эти части взаимодействуют. В моей версии feature-based архитектуры тоже есть чёткое разделение, но в отличие от FSD, в центре стоит предметная область и функциональное мышление.
Главное — понимать, что архитектура — это очень широкое и абстрактное понятие, и к программированию оно относится не только через код, но и через процессы, структуру команд и даже инфраструктуру. Поэтому раскладывать код по папкам — это один из важных, но не единственных, аспектов архитектуры.
👍4❤1
Малоизвестные возможности Intl
Intl часто ассоциируется с простым форматированием чисел, дат и валют. Однако это API содержит и другие полезные функции:
Intl.ListFormat — форматирует перечисления, корректно вставляя запятые и союзы:
Intl.RelativeTimeFormat — форматирует относительное время:
Intl.DurationFormat — форматирует интервалы времени:
Intl.Segmenter — позволяет разбивать строки на сегменты с учётом особенностей локали и правил Юникод, что обеспечивает корректную работу даже для сложных языков и символов.
Например, в отличие от обычного .split(""), Segmenter не разбивает эмодзи:
Intl часто ассоциируется с простым форматированием чисел, дат и валют. Однако это API содержит и другие полезные функции:
Intl.ListFormat — форматирует перечисления, корректно вставляя запятые и союзы:
// "яблоки, груши и бананы"
(new Intl.ListFormat("RU")).format(
["яблоки", "груши", "бананы"]
);
Intl.RelativeTimeFormat — форматирует относительное время:
const rtf = new Intl.RelativeTimeFormat("RU");
rtf.format(3, "days"); // "через 3 дня"
rtf.format(-2, "hours"); // "2 часа назад"
Intl.DurationFormat — форматирует интервалы времени:
const time = { hours: 2, minutes: 35 };
// 2 часа 35 минут
(new Intl.DurationFormat("RU", {
style: "long",
})).format(time);
// 02:35:00
(new Intl.DurationFormat("RU", {
hours: "2-digit",
})).format(time);
Intl.Segmenter — позволяет разбивать строки на сегменты с учётом особенностей локали и правил Юникод, что обеспечивает корректную работу даже для сложных языков и символов.
// ["Несколько", " ", "слов", " ", "в", " ", "строке", " ", "😎", "."]
Array.from(
(new Intl.Segmenter("RU", { granularity: "word" }))
.segment("Несколько слов в строке 😎.")
)
.map((item) => `"${item.segment}"`);
Например, в отличие от обычного .split(""), Segmenter не разбивает эмодзи:
// ['\uD83D', '\uDE0E']
"😎".split("");
// => [{segment: "😎"}]
[...(new Intl.Segmenter("RU")).segment("😎")];
🔥8
Несколько слов о TanStack Query (React Query)
В документации говорится, что TanStack Query упрощает получение, кеширование, синхронизацию и обновление данных. В целом это так, но есть нюансы.
Рассмотрим базовый пример:
Казалось бы, всё просто: есть состояние загрузки, ошибка и данные. Но давайте разберёмся чуть глубже.
Как вы думаете, столько параметров состояния возвращает useQuery? Вот неполный список: status, isPending, isSuccess, isError, isLoadingError, isRefetchError, isFetched, isFetchedAfterMount, fetchStatus, isFetching, isRefetching, isLoading, isInitialLoading… Сможете без документации разобраться, чем отличаются, например, isPending, isLoading и isFetching?
А сколько методов возвращает useQuery? Всего один — refetch, хотя библиотека предоставляет и другие необходимые функции, например setQueryData (для обновления данных) и invalidateQueries (для инвалидации).
Но чтобы ими воспользоваться, нужно знать queryKey запроса, а также получить инстанс queryClient через хук useQueryClient (или прямой импорт):
Как-то многовато кода добавилось из-за одной простой операции…
TanStack Query действительно упрощает определённые операции, но у всего есть своя цена, и понимать её, как обычно, начинаешь только после длительного использования. Большое количество возможностей в одном инструменте — это как плюс, так и минус (особенно для больших команд). К тому же, «из коробки» вам придётся писать довольно много шаблонного кода, а в последствии думать как его сокращать.
В документации говорится, что TanStack Query упрощает получение, кеширование, синхронизацию и обновление данных. В целом это так, но есть нюансы.
Рассмотрим базовый пример:
import { useQuery } from "@tanstack/react-query";
export function Example() {
const query = useQuery({
queryKey: ['example', 'data'],
queryFn: () => {
return fetch('/api/example/data')
.then((res) => res.json());
},
});
if (query.isLoading) return 'Loading...';
if (query.error) return 'An error has occurred';
return <div>{query.data}</div>;
}
Казалось бы, всё просто: есть состояние загрузки, ошибка и данные. Но давайте разберёмся чуть глубже.
Как вы думаете, столько параметров состояния возвращает useQuery? Вот неполный список: status, isPending, isSuccess, isError, isLoadingError, isRefetchError, isFetched, isFetchedAfterMount, fetchStatus, isFetching, isRefetching, isLoading, isInitialLoading… Сможете без документации разобраться, чем отличаются, например, isPending, isLoading и isFetching?
А сколько методов возвращает useQuery? Всего один — refetch, хотя библиотека предоставляет и другие необходимые функции, например setQueryData (для обновления данных) и invalidateQueries (для инвалидации).
Но чтобы ими воспользоваться, нужно знать queryKey запроса, а также получить инстанс queryClient через хук useQueryClient (или прямой импорт):
import { useQuery, useQueryClient } from "@tanstack/react-query";
function getExampleApiKey() {
return ['example', 'data'];
}
export function Example() {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: getExampleApiKey(),
queryFn: () => {
return fetch('/api/example/data')
.then((res) => res.json());
},
});
function handleInvalidate() {
void queryClient.invalidateQueries({
queryKey: getExampleApiKey(),
});
}
if (query.isLoading) return 'Loading...';
if (query.error) return 'An error has occurred';
return (
<div>
<div>{query.data}</div>
<button onClick={handleInvalidate}>
Update
</button>
</div>
);
}
Как-то многовато кода добавилось из-за одной простой операции…
TanStack Query действительно упрощает определённые операции, но у всего есть своя цена, и понимать её, как обычно, начинаешь только после длительного использования. Большое количество возможностей в одном инструменте — это как плюс, так и минус (особенно для больших команд). К тому же, «из коробки» вам придётся писать довольно много шаблонного кода, а в последствии думать как его сокращать.
🌚3💯3💩1🤣1🗿1
Фиксированное соотношение сторон в CSS
Задача: сверстать адаптивные блоки, которые будут сохранять соотношение сторон (например квадрат) при изменении ширины родительского элемента.
Старое решение:
Требует дополнительного элемента в разметке.
Новое решение (Chrome 105+, Safari 16+):
Лучшее решение (Chrome 88+, Safari 15+):
П.С. В качестве контейнера можно использовать как flex так и grid, однако стили блоков будут слегка отличаться.
Задача: сверстать адаптивные блоки, которые будут сохранять соотношение сторон (например квадрат) при изменении ширины родительского элемента.
Старое решение:
Требует дополнительного элемента в разметке.
<div class="grid">
<div class="card">
<div class="content">
...
</div>
</div>
...
</div>
.grid {
display: flex;
flex-wrap: wrap;
}
.card {
position: relative;
padding-top: 33.33%; /* !!! */
width: 33.33%;
}
.content {
position: absolute;
top: 0;
width: 100%;
height: 100%;
}
Новое решение (Chrome 105+, Safari 16+):
<div class="grid">
<div class="card">
...
</div>
...
</div>
.grid {
container-type: size; /* !!! */
display: flex;
flex-wrap: wrap;
}
.card {
width: 33.33%;
height: 33.33cqw; /* !!! */
}
Лучшее решение (Chrome 88+, Safari 15+):
.grid {
display: flex;
flex-wrap: wrap;
}
.card {
width: 33.33%;
aspect-ratio: 1 / 1; /* !!! */
}
П.С. В качестве контейнера можно использовать как flex так и grid, однако стили блоков будут слегка отличаться.
🔥7
Проверяем импорты через ESLint
В процессе улучшения структуры проекта, которую я описывал здесь, задумался над базовыми проверками корректности импортов. Эта структура строится на «функциональном мышлении» (далее ФМ), в котором код должен быть разделён на данные, вычисления и действия, поэтому необходимо проверять корректность импортов между определёнными сегментами. Например, чтобы сегмент ui (в контексте ФМ это вычисления) не мог импортировать сегмент $api (в контексте ФМ это действия). На самом деле причин и проверок больше, но не суть.
Для эксперимента был выбран eslint-plugin-boundaries. В целом неплохой инструмент, хотя сильно не хватает разделения контекста, но разработка в этом направлении вроде как ведётся. В моём случае, например, необходимы разные правила для проверки верхнего уровня папок и уровня сегментов.
eslint.config.js
eslint-boundaries.js
Полная версия файла тут.
Да, все возможные варианты этот код не покрывает, но для начала этих проверок вполне достаточно.
В процессе улучшения структуры проекта, которую я описывал здесь, задумался над базовыми проверками корректности импортов. Эта структура строится на «функциональном мышлении» (далее ФМ), в котором код должен быть разделён на данные, вычисления и действия, поэтому необходимо проверять корректность импортов между определёнными сегментами. Например, чтобы сегмент ui (в контексте ФМ это вычисления) не мог импортировать сегмент $api (в контексте ФМ это действия). На самом деле причин и проверок больше, но не суть.
Для эксперимента был выбран eslint-plugin-boundaries. В целом неплохой инструмент, хотя сильно не хватает разделения контекста, но разработка в этом направлении вроде как ведётся. В моём случае, например, необходимы разные правила для проверки верхнего уровня папок и уровня сегментов.
eslint.config.js
import { defineConfig } from 'eslint/config';
import parser from '@typescript-eslint/parser';
import boundaries from './eslint-boundaries.js';
export default defineConfig([
{
extends: [boundaries],
files: ['src/**/*.{ts,tsx}'],
languageOptions: { parser },
settings: { 'import/resolver': { typescript: true } },
},
]);
eslint-boundaries.js
import boundariesPlugin from 'eslint-plugin-boundaries';
export default {
plugins: {
boundaries: boundariesPlugin,
},
settings: {
'boundaries/elements': [
{ type: '$control', pattern: 'src/*/$control/**' },
{ type: '_core', pattern: 'src/_core/*', mode: 'full' },
// ...
],
},
rules: {
'boundaries/element-types': [
'warn', {
default: 'allow',
message: '[${file.type}] is not allowed to import [${dependency.type}]',
rules: [
{ from: '$control', disallow: ['$page'] },
{ from: '_core', disallow: ['_ui-kit', 'admin', 'app'] },
// ...
],
},
],
},
};
Полная версия файла тут.
Да, все возможные варианты этот код не покрывает, но для начала этих проверок вполне достаточно.
🔥2
Что за звери такие cohesion и coupling
Возможно, вы слышали, что при выборе архитектуры (или её проектировании) нужно стремиться к high cohesion (высокому сцеплению) и low coupling (низкой связанности). Так вот, cohesion и coupling — два базовых критерия оценки качества архитектуры.
Cohesion — это то, насколько части одного модуля логически относятся к одной задаче. Чем выше cohesion, тем больше «про одно» этот модуль, тем проще его понять и менять. Можно даже сказать, что high cohesion это принцип единой ответственности из SOLID.
Coupling — это то, насколько один модуль зависит от других. Чем ниже coupling, тем меньше модули знают друг о друге и тем легче менять их независимо.
В общем, хорошая архитектура, это такая, где каждый модуль или фича — самодостаточный «чёрный ящик» с высокой cohesion внутри (всё логически про одну задачу, без лишнего мусора) и низким coupling снаружи (минимальные зависимости), чтобы код легко масштабировался, менялся и тестировался без цепной реакции по всему проекту.
И тут вы справедливо можете заметить, что в реальном мире всё несколько сложнее, и да, вы будете абсолютно правы.
П.С. Дедовский лайфхак, как проверить, что у вас хорошая (или не очень) архитектура: попробуйте поудалять разные (конечные) модули вашей системы. Чем меньше изменений придётся вносить в оставшуюся часть системы, тем лучше архитектура.
Возможно, вы слышали, что при выборе архитектуры (или её проектировании) нужно стремиться к high cohesion (высокому сцеплению) и low coupling (низкой связанности). Так вот, cohesion и coupling — два базовых критерия оценки качества архитектуры.
Cohesion — это то, насколько части одного модуля логически относятся к одной задаче. Чем выше cohesion, тем больше «про одно» этот модуль, тем проще его понять и менять. Можно даже сказать, что high cohesion это принцип единой ответственности из SOLID.
Coupling — это то, насколько один модуль зависит от других. Чем ниже coupling, тем меньше модули знают друг о друге и тем легче менять их независимо.
В общем, хорошая архитектура, это такая, где каждый модуль или фича — самодостаточный «чёрный ящик» с высокой cohesion внутри (всё логически про одну задачу, без лишнего мусора) и низким coupling снаружи (минимальные зависимости), чтобы код легко масштабировался, менялся и тестировался без цепной реакции по всему проекту.
И тут вы справедливо можете заметить, что в реальном мире всё несколько сложнее, и да, вы будете абсолютно правы.
П.С. Дедовский лайфхак, как проверить, что у вас хорошая (или не очень) архитектура: попробуйте поудалять разные (конечные) модули вашей системы. Чем меньше изменений придётся вносить в оставшуюся часть системы, тем лучше архитектура.
🔥3🤔1
Знакомая всем вам двоичная система счисления, которая используется практически во всех современных компьютерах, была описана Г. В. Лейбницем в 17 веке. Важный момент тут в том, что она была именно описана, а не изобретена, ведь как известно, всё уже изобретено до нас. Идеи двоичной записи появились за долго до Лейбница, например в древнекитайской «Книге Перемен» о которой он точно знал, как человек увлекающийся китайской культурой. На минутку, возраст этой книги около 3000 лет! Книга Перемен — философский текст, включающий 64 символа, каждый из которых состоит из 6 черт (сплошных или прерывистых, что соответствует 1 и 0).
Хорошие идеи часто лежат на виду, просто в других плоскостях. Чем шире ваш кругозор, тем больше связей вы замечаете и тем оригинальнее становятся ваши решения.
Расширяйте свои горизонты!
С Новым годом!
Хорошие идеи часто лежат на виду, просто в других плоскостях. Чем шире ваш кругозор, тем больше связей вы замечаете и тем оригинальнее становятся ваши решения.
Расширяйте свои горизонты!
С Новым годом!
🎄4🔥1
Самые изменяемые файлы проекта, о чём говорят и как посмотреть
Частота изменения файлов — интересная метрика, способная подсветить места повышенных рисков, архитектурных проблем и некоторых других моментов. Для более полной картины происходящего, рекомендуется рассматривать её в совокупности с такой информацией, как количество строк в файле, сложность кода, связи между файлами, количество задач и их связанность.
Собрать список самых изменяемых файлов, можно, например, с помощью
Важная ремарка: не всегда полученный список будет отражать что-то полезное. Вполне может быть так, что в нём будут одни index, config, types и тому подобные файлы. Хотя, слишком частое изменение одного и того же index файла — тоже, своего рода, знак.
В моём случае, после беглого изучения файлов из полученного списка, первое что бросилось в глаза — размер: 670, 450, 490 и 390 строк кода. Многовато, особенно в первом из них. На нём и заострим своё внимание.
Из полученного списка коммитов, путём не хитрых манипуляций, извлекаем авторов, а главное — список задач, которых оказалось не мало, они имели слабую связь, а часть из них вообще была исправлением ошибок.
Не забывайте вставлять номера задач в каждый коммит, это помогает в подобных случаях, а также в случаях когда нужно понять, почему были внесены те или иные изменения.
Всё это: частота изменения файла, количество строк кода, количество и (не) связанность задач, в данном случае, явно сообщает нам о том, что это типичный «god-компонент» (в нашем случае хук) и первый кандидат на ближайший рефакторинг, а также требует особого внимание на ревью и даже в тестировании.
Частота изменения файлов — интересная метрика, способная подсветить места повышенных рисков, архитектурных проблем и некоторых других моментов. Для более полной картины происходящего, рекомендуется рассматривать её в совокупности с такой информацией, как количество строк в файле, сложность кода, связи между файлами, количество задач и их связанность.
Собрать список самых изменяемых файлов, можно, например, с помощью
git log.Важная ремарка: не всегда полученный список будет отражать что-то полезное. Вполне может быть так, что в нём будут одни index, config, types и тому подобные файлы. Хотя, слишком частое изменение одного и того же index файла — тоже, своего рода, знак.
git log master \
--name-only \
--pretty=format: \
--since="6.month" \
-- apps/***/src/ \
| sort | uniq -c | sort -nr | head -5
1564
53 apps/***/use-some-dialog.ts
42 apps/***/submit.tsx
31 apps/***/main.tsx
26 apps/***/some-content.tsx
В моём случае, после беглого изучения файлов из полученного списка, первое что бросилось в глаза — размер: 670, 450, 490 и 390 строк кода. Многовато, особенно в первом из них. На нём и заострим своё внимание.
git log master \
--no-merges \
--since="6.month" \
--pretty=format:"%ae %ad %s" --date=short \
apps/***/use-some-dialog.ts \
| head -100
Из полученного списка коммитов, путём не хитрых манипуляций, извлекаем авторов, а главное — список задач, которых оказалось не мало, они имели слабую связь, а часть из них вообще была исправлением ошибок.
Не забывайте вставлять номера задач в каждый коммит, это помогает в подобных случаях, а также в случаях когда нужно понять, почему были внесены те или иные изменения.
Всё это: частота изменения файла, количество строк кода, количество и (не) связанность задач, в данном случае, явно сообщает нам о том, что это типичный «god-компонент» (в нашем случае хук) и первый кандидат на ближайший рефакторинг, а также требует особого внимание на ревью и даже в тестировании.
❤7
Императив vs Декларатив. Точно понимаете разницу?
Императивный стиль описывает КАК достичь результата (пошаговые инструкции), а декларативный — ЧТО нужно получить (описание желаемого исхода).
Скорее всего, именно так вы ответите на вопрос, чем отличаются эти стили программирования, и по сути вы будете правы. Но давайте рассмотрим простой пример:
Сможете чётко ответить, какой именно стиль программирования тут используется, ЧТО или КАК? Если вы засомневались или подумали что это императивный стиль, то добро пожаловать в клуб, т.к. это декларативный стиль.
Вместо использования КАК и ЧТО, предлагаю следующее определение:
Императивный стиль характеризуется явным выполнением операций, которые что‑то «делают» во «внешнем мире» (мутируют данные, вызывают системные API и т.п.), тогда как декларативный стиль сводится к написанию чистых выражений и функций, которые лишь вычисляют значения.
Кстати, если вы помните (и понимаете) концепцию функционального мышления, то в целом можно сказать что императивный стиль — «действия», а декларативный — «вычисления».
Императивный стиль описывает КАК достичь результата (пошаговые инструкции), а декларативный — ЧТО нужно получить (описание желаемого исхода).
Скорее всего, именно так вы ответите на вопрос, чем отличаются эти стили программирования, и по сути вы будете правы. Но давайте рассмотрим простой пример:
const isEven = count % 2 === 0;
Сможете чётко ответить, какой именно стиль программирования тут используется, ЧТО или КАК? Если вы засомневались или подумали что это императивный стиль, то добро пожаловать в клуб, т.к. это декларативный стиль.
Вместо использования КАК и ЧТО, предлагаю следующее определение:
Императивный стиль характеризуется явным выполнением операций, которые что‑то «делают» во «внешнем мире» (мутируют данные, вызывают системные API и т.п.), тогда как декларативный стиль сводится к написанию чистых выражений и функций, которые лишь вычисляют значения.
Кстати, если вы помните (и понимаете) концепцию функционального мышления, то в целом можно сказать что императивный стиль — «действия», а декларативный — «вычисления».
👍1👎1🔥1
Простой пример сокрытия императивного кода за декларативным интерфейсом
Для начала вспомним, что декларативное программирование фокусируется на описании желаемого результата, вместо указания пошаговых действий. Основное преимущество этого стиля в том, что он делает код короче и выразительнее, скрывая детали реализации.
Простой пример, где isLoading отвечает за блокировку кнопки Submit:
Императивным тут является только onSubmit, который, помимо основной задачи, контролирует состояние формы через setLoading.
Разделяем ответственность и скрываем часть императивного кода внутри useLoading:
Теперь onSubmit выглядит чище и отвечает только за то, что и должен.
П.С. Для SolidJS useLoading может выглядеть так:
Для начала вспомним, что декларативное программирование фокусируется на описании желаемого результата, вместо указания пошаговых действий. Основное преимущество этого стиля в том, что он делает код короче и выразительнее, скрывая детали реализации.
Простой пример, где isLoading отвечает за блокировку кнопки Submit:
function CreateOrder(props) {
const [isLoading, setLoading] = createSignal(false);
function onSubmit() {
setLoading(true);
props.onSubmit()
.then(() => {/* ... */})
.catch(() => {/* ... */})
.finally(() => setLoading(false));
}
return <Form onSubmit={onSubmit}>...</Form>;
}
Императивным тут является только onSubmit, который, помимо основной задачи, контролирует состояние формы через setLoading.
Разделяем ответственность и скрываем часть императивного кода внутри useLoading:
function CreateOrder(props) {
const [isLoading, submit] = useLoading(props.onSubmit);
function onSubmit() {
submit()
.then(() => {/* ... */})
.catch(() => {/* ... */});
}
return <Form onSubmit={onSubmit}>...</Form>;
}
Теперь onSubmit выглядит чище и отвечает только за то, что и должен.
П.С. Для SolidJS useLoading может выглядеть так:
import { type Accessor, createSignal } from 'solid-js';
export function useLoading<
T,
P extends any[],
A extends ((...args: P) => Promise<T>) | undefined
>(action: A): [Accessor<boolean>, A] {
const [loading, setLoading] = createSignal(false);
const handler = action && (((...args: P): Promise<T> => {
setLoading(true);
return action(...args).finally(() => setLoading(false));
}) as A);
return [loading, handler];
}
Vite vs Webpack: нюансы сборки
В проекте на Vite обнаружил неожиданное (для меня) поведение: при загрузке чанка страницы, браузер также загружает чанки двух других, не связанных с ней, страниц:
Почему так? Vite (rollup) помещает общий компонент, который используется в нескольких асинхронных чанках, в один из них. Другими словами, при загрузке любого из этих чанков, браузер вынужден загрузить и тот чанк, в котором находятся общий компонент.
Однако, это не является поведением по умолчанию. Всему виной параметр experimentalMinChunkSize. Без него, Vite выносит все общие компоненты в отдельные чанки. Даже если компонент весит всего 106 байт, как этот:
В итоге получается, либо много маленьких чанков (поведение по умолчанию), либо браузер загружает много не связанного кода (при использовании параметра).
На заметку: experimentalMinChunkSize работает не всегда и в проекте всё равно остаются микро-чанки.
Webpack работает иначе: помещает копию компонента в каждый асинхронный чанк, где этот компонент необходим, если его размер не превышает значения указанного в splitChunks.minSize (по умолчанию 20000 байт). В противном случае, компонент выносится в отдельный чанк.
В проекте на Vite обнаружил неожиданное (для меня) поведение: при загрузке чанка страницы, браузер также загружает чанки двух других, не связанных с ней, страниц:
./page-a.js
./page-b.js
./page-c.js
Почему так? Vite (rollup) помещает общий компонент, который используется в нескольких асинхронных чанках, в один из них. Другими словами, при загрузке любого из этих чанков, браузер вынужден загрузить и тот чанк, в котором находятся общий компонент.
Однако, это не является поведением по умолчанию. Всему виной параметр experimentalMinChunkSize. Без него, Vite выносит все общие компоненты в отдельные чанки. Даже если компонент весит всего 106 байт, как этот:
import {c as o} from "./createIcon.js";
const c = o("24", "Stroked", "Cancel");
export {c as C};
В итоге получается, либо много маленьких чанков (поведение по умолчанию), либо браузер загружает много не связанного кода (при использовании параметра).
На заметку: experimentalMinChunkSize работает не всегда и в проекте всё равно остаются микро-чанки.
Webpack работает иначе: помещает копию компонента в каждый асинхронный чанк, где этот компонент необходим, если его размер не превышает значения указанного в splitChunks.minSize (по умолчанию 20000 байт). В противном случае, компонент выносится в отдельный чанк.
👍8🤔1