На счёт предыдущего поста. Если очень сильно хочется как в SolidJS но не хочется/можется уйти от React, то есть вот такой пакет: https://www.npmjs.com/package/@preact/signals-react
🔥1
Причина закрытия WebSocket соединения
Если вы работали с WebSocket без каких либо обёрток, возможно вы обратили внимание, что метод close может принимать два параметра: код и причину закрытия. А CloseEvent, в свою очередь, оба эти значения может содержать.
Однако, этот код не гарантирует что в логе будет 'Reason'! А всё потому, что при вызове метода close, сначала отправляется соответствующая команда на сервер, который кстати может и не ответить. Но даже если он ответил, нет гарантий что он вернёт нам эту же причину.
В общем, CloseEvent.reason — причина закрытия с сервера, поэтому там может быть всё что угодно.
П.С. Есть ещё CloseEvent.wasClean, по которому можно понять было ли соединение закрыто “чисто” или что-то пошло не так.
#development #frontend #javascript
Если вы работали с WebSocket без каких либо обёрток, возможно вы обратили внимание, что метод close может принимать два параметра: код и причину закрытия. А CloseEvent, в свою очередь, оба эти значения может содержать.
const socket = new WebSocket(url);
socket.onclose = (event) => {
console.log(event.reason);
};
socket.close(1000, 'Reason');
Однако, этот код не гарантирует что в логе будет 'Reason'! А всё потому, что при вызове метода close, сначала отправляется соответствующая команда на сервер, который кстати может и не ответить. Но даже если он ответил, нет гарантий что он вернёт нам эту же причину.
В общем, CloseEvent.reason — причина закрытия с сервера, поэтому там может быть всё что угодно.
П.С. Есть ещё CloseEvent.wasClean, по которому можно понять было ли соединение закрыто “чисто” или что-то пошло не так.
#development #frontend #javascript
👍2👎1
До кучи, вот небольшое приложение https://github.com/SanichKotikov/ws-test-app/ Писал чтобы потестировать как WebSocket будет себя вести на мобильных устройствах, в том числе в PWA. Там же есть ссылка на уже развёрнутое приложение.
GitHub
GitHub - SanichKotikov/ws-test-app
Contribute to SanichKotikov/ws-test-app development by creating an account on GitHub.
👍2
Принудительное выполнение компонента в SolidJS
Помним, что компоненты в SolidJS, в отличие от React, выполняются только “один” раз. Однако, бывают случаи когда необходимо выполнить компонент “заново” при изменении каких-то данных. Например, нужно полностью обновить форму редактирования при выборе другого блюда:
Параметр keyed компонента Show как раз для этого и предназначен. При этом, в параметре when необходимо указать значение, при изменении которого произойдёт повторное выполнение.
#development #frontend #javascript #solidjs
Помним, что компоненты в SolidJS, в отличие от React, выполняются только “один” раз. Однако, бывают случаи когда необходимо выполнить компонент “заново” при изменении каких-то данных. Например, нужно полностью обновить форму редактирования при выборе другого блюда:
<Resource data={dishData} onReload={dishActions.refetch}>
{(dish) => (
<Show keyed when={dish().id}>
<DishForm
dish={dish()}
categories={categories()}
onSubmit={onDishSave}
/>
</Show>
)}
</Resource>
Параметр keyed компонента Show как раз для этого и предназначен. При этом, в параметре when необходимо указать значение, при изменении которого произойдёт повторное выполнение.
#development #frontend #javascript #solidjs
🔥1
TypeScript + .filter(Boolean)
Тип переменной foo будет соответствовать
Обычно предлагается следующее решение:
Но есть вариант получше:
Теперь
П.С. Есть аналогичный “хак” и для Object.keys (хотя он и не совсем корректен):
#development #frontend #typescript
const numbers = [1, 2, null, void 0, 3];
const foo = numbers.filter(Boolean);
Тип переменной foo будет соответствовать
(number | null | undefined)[], хотя в действительности ни null ни undefined в массиве не будет.Обычно предлагается следующее решение:
numbers.filter((n): n is number => !!n);
Но есть вариант получше:
// global.d.ts
type Falsy = false | '' | 0 | 0n | null | undefined;
type Truthy<T> = T extends Falsy ? never : T;
interface Array<T> {
filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
filter<S extends T>(predicate: BooleanConstructor, thisArg?: any): Truthy<S>[];
}
interface ReadonlyArray<T> {
filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
filter<S extends T>(predicate: BooleanConstructor, thisArg?: any): Truthy<S>[];
}
Теперь
numbers.filter(Boolean) будет возвращать number[].П.С. Есть аналогичный “хак” и для Object.keys (хотя он и не совсем корректен):
// global.d.ts
interface ObjectConstructor {
keys<T extends {}>(obj: T): ReadonlyArray<keyof T>;
}
#development #frontend #typescript
👍2🔥2
Бизнес логика vs функциональное мышление
Бизнес логика — весьма абстрактное понятие. Разработчики до сих пор продолжаются “сраться” на тему того, что считать бизнес логикой, а самое главное где она должна быть. Как один из примеров, предлагается держать бизнес логику исключительно в “умных” компонентах.
Напомню базовое определение:
> Бизнес логика — совокупность правил, принципов, зависимостей поведения объектов предметной области.
Под него подходят, как сложные вычисления на беке, так и правила по которым устанавливается размер и цвет кнопок на фронте. В данном контексте довольно сложно говорить о том, чтобы держать бизнес логику только в “умных” компонентов. При таком подходе, их количество будет стремиться к бесконечности, а для “глупых” может не остаться места.
Не знаю как вас, но меня всегда смущала эта широта трактовки бизнес логики. И сложность эта говорит о том, что возможно этот термин вообще стоит обходить стороной, и уж тем более не завязывать на него какие-то правила построения приложения.
К тому же, есть отличная альтернатива — функциональное мышление (проектирование), о котором я писал (https://t.me/around_dev/48) не так давно. Этот подход подразумевает разделение всего кода на три составляющие: данные, вычисления и действия. При этом, деление на “умные” и “глупые” компоненты прекрасно ложится на этот подход, где глупые компоненты это вычисления, а умные — действия.
#development
Бизнес логика — весьма абстрактное понятие. Разработчики до сих пор продолжаются “сраться” на тему того, что считать бизнес логикой, а самое главное где она должна быть. Как один из примеров, предлагается держать бизнес логику исключительно в “умных” компонентах.
Напомню базовое определение:
> Бизнес логика — совокупность правил, принципов, зависимостей поведения объектов предметной области.
Под него подходят, как сложные вычисления на беке, так и правила по которым устанавливается размер и цвет кнопок на фронте. В данном контексте довольно сложно говорить о том, чтобы держать бизнес логику только в “умных” компонентов. При таком подходе, их количество будет стремиться к бесконечности, а для “глупых” может не остаться места.
Не знаю как вас, но меня всегда смущала эта широта трактовки бизнес логики. И сложность эта говорит о том, что возможно этот термин вообще стоит обходить стороной, и уж тем более не завязывать на него какие-то правила построения приложения.
К тому же, есть отличная альтернатива — функциональное мышление (проектирование), о котором я писал (https://t.me/around_dev/48) не так давно. Этот подход подразумевает разделение всего кода на три составляющие: данные, вычисления и действия. При этом, деление на “умные” и “глупые” компоненты прекрасно ложится на этот подход, где глупые компоненты это вычисления, а умные — действия.
#development
🔥1
Vite vs Webpack: PURE комментарии
В продолжении поста “нюанс с React.memo” и перед темой про sideEffects хочется ещё немного накинуть про разницу в сборке проекта с помощью Vite и Webpack.
Расширим прошлый пример до:
Примечание: Из всего этого, в коде используется только компонент Test.
Собрав проект мы получаем следующие результаты:
Webpack оставил в бандле только Test и сайд эффекты, а все остальные компоненты и вспомогательные функции он удалил.
Vite же оставил всё кроме компонента TestRaw…
Ну что ж, если вы читали прошлый пост, то результат для вас ожидаем. Но возможно вас удивит тот момент, что в обоих случаях файл lazy.js (в котором лежит тот самый ленивый компонент LazyTest) присутствует в сборке. С Vite всё понятно, но почему Webpack оставил этот файл хотя компонент не используется? Давайте разбираться.
На самом деле собирается весь код до которого сборщик может дотянуться. Попутно проставляются специальные PURE комментарии-подсказки, например как в этом случае:
Далее, в дело вступает минификатор кода, именно он и удаляет всё что посчитает необходимым (что по его мнению не используется). И PURE комментарии играют в этом процессе важную роль, сообщая что следующий код “чистый” и может быть удалён, если не используется.
Но и тут Vite действует иначе… удаление не используемого кода происходит до этапа минификации (обратите внимание, он удалил константу TestMemo).
Что касается вопроса про lazy.js, то сам файл создаётся в момент сборки, а минификатор работает лишь с содержимым уже созданных файлов, поэтому файл и остаётся в бандле, хотя и не используется.
П.С. Кстати, именно поэтому анализаторы бандла могут показывать то, чего в бандле на самом деле нет.
#development #frontend #webpack #vite
В продолжении поста “нюанс с React.memo” и перед темой про sideEffects хочется ещё немного накинуть про разницу в сборке проекта с помощью Vite и Webpack.
Расширим прошлый пример до:
// Простой компонент
export { Test } from './test';
// Ещё один простой компонент
export { TestRaw } from './test-raw';
// Компонент обёрнутый в React.memo
export { TestMemo } from './test-memo';
// Компонент обёрнутый в React.forwardRef
export { TestRef } from './test-ref';
// Ленивый компонент
// через функцию поверх React.lazy
export { LazyTest } from './lazy';
Примечание: Из всего этого, в коде используется только компонент Test.
Собрав проект мы получаем следующие результаты:
Webpack оставил в бандле только Test и сайд эффекты, а все остальные компоненты и вспомогательные функции он удалил.
Vite же оставил всё кроме компонента TestRaw…
Ну что ж, если вы читали прошлый пост, то результат для вас ожидаем. Но возможно вас удивит тот момент, что в обоих случаях файл lazy.js (в котором лежит тот самый ленивый компонент LazyTest) присутствует в сборке. С Vite всё понятно, но почему Webpack оставил этот файл хотя компонент не используется? Давайте разбираться.
На самом деле собирается весь код до которого сборщик может дотянуться. Попутно проставляются специальные PURE комментарии-подсказки, например как в этом случае:
// Фрагмент из сборки от Webpack:
const TestMemo = /*#__PURE__*/(null && (memo(() => {
return /*#__PURE__*/_jsx("p", {
children: "MEMO"
});
})));
// Для сравнения, тот же фрагмент от Vite:
react.memo(() => {
return /* @__PURE__ */ jsxRuntime.jsx("p", {
children: "MEMO"
});
});
Далее, в дело вступает минификатор кода, именно он и удаляет всё что посчитает необходимым (что по его мнению не используется). И PURE комментарии играют в этом процессе важную роль, сообщая что следующий код “чистый” и может быть удалён, если не используется.
Но и тут Vite действует иначе… удаление не используемого кода происходит до этапа минификации (обратите внимание, он удалил константу TestMemo).
Что касается вопроса про lazy.js, то сам файл создаётся в момент сборки, а минификатор работает лишь с содержимым уже созданных файлов, поэтому файл и остаётся в бандле, хотя и не используется.
П.С. Кстати, именно поэтому анализаторы бандла могут показывать то, чего в бандле на самом деле нет.
#development #frontend #webpack #vite
🔥1
Комментарии в коде — “хорошо” или “плохо”?
Наткнувшись, в очередной раз, на споры о комментариях в коде, решил “зафиксировать” свои мысли на этот счёт.
Вопреки популярному мнению, заметное количество комментариев в коде — это не признак хорошего кода, а намёк на “код с душком”. Я уж молчу про примеры типа “мне тут лень было разбираться поэтому…”
В идеале, хороший код должен быть самодокументируемым. Понятные названия и типы, небольшие функции, явная структура и т.д. Комментарии действительно нужны лишь в ограниченном количестве случаев. Например, когда вы описываете публичный интерфейс, когда умышленно жертвуете читаемостью в пользу оптимизации и т.п.
В общем, комментарии — не хорошо и не плохо, это инструмент которым нужно уметь пользоваться, а не прикрывать им плохой код.
Наткнувшись, в очередной раз, на споры о комментариях в коде, решил “зафиксировать” свои мысли на этот счёт.
Вопреки популярному мнению, заметное количество комментариев в коде — это не признак хорошего кода, а намёк на “код с душком”. Я уж молчу про примеры типа “мне тут лень было разбираться поэтому…”
В идеале, хороший код должен быть самодокументируемым. Понятные названия и типы, небольшие функции, явная структура и т.д. Комментарии действительно нужны лишь в ограниченном количестве случаев. Например, когда вы описываете публичный интерфейс, когда умышленно жертвуете читаемостью в пользу оптимизации и т.п.
В общем, комментарии — не хорошо и не плохо, это инструмент которым нужно уметь пользоваться, а не прикрывать им плохой код.
👍4
Насколько 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