Typescript: Union типы на практике
Представим, что мы пишем свою реализацию “хранилища” данных, которое должно возвращать примерно такое состояние:
На первый взгляд может показаться что всё ок, однако это не совсем так. К примеру, проверка status ничего не скажет нам о состоянии других полей:
Решаем проблему через union тип, добавляя три (по количеству состояний) отдельных интерфейса в каждом из которых error и data напрямую зависят от значения status:
Проверяем наш код, и видим что всё работает без ошибок 🎉:
Кстати, тоже самое можно провернуть и с типизацией props для React компонента, который должен принимать разный набор параметров в зависимости от значения одного общего.
#typescript #frontend #development #react
Представим, что мы пишем свою реализацию “хранилища” данных, которое должно возвращать примерно такое состояние:
interface State<T = unknown> {
status: 'loading' | 'error' | 'success',
error: Error | undefined,
data: T | undefined,
}
На первый взгляд может показаться что всё ок, однако это не совсем так. К примеру, проверка status ничего не скажет нам о состоянии других полей:
if (state.status === 'success') {
console.log(state.data.toFixed());
// Получаем ошибку
// 'state.data' is possibly 'undefined'.
}
Решаем проблему через union тип, добавляя три (по количеству состояний) отдельных интерфейса в каждом из которых error и data напрямую зависят от значения status:
interface LoadingState {
status: 'loading';
error: undefined;
data: undefined;
}
interface ErrorState {
status: 'error';
error: Error;
data: undefined;
}
interface SuccessState<T = unknown> {
status: 'success';
error: undefined;
data: T;
}
type State<T = unknown> =
| LoadingState
| ErrorState
| SuccessState<T>;
Проверяем наш код, и видим что всё работает без ошибок 🎉:
if (state.status === 'success') {
console.log(state.data.toFixed());
}
if (state.status === 'error') {
console.log(state.error.message);
}
Кстати, тоже самое можно провернуть и с типизацией props для React компонента, который должен принимать разный набор параметров в зависимости от значения одного общего.
type GridProps = SharedProps & (GridTypeProps | FlexTypeProps);
#typescript #frontend #development #react
👾1
Про чанки
Начать нужно с того, что весь (frontend) код который мы пишем, в конечном итоге загружается пользователями. Помимо самой загрузки, браузеру нужно этот код прочитать, проанализировать и выполнить. На всё это, как ни странно, нужно время, и чем больше этого кода, тем больше времени будут занимать все эти процессы.
Стоит сделать важное замечание: даже если нужно выполнить какую-то небольшую часть кода, загрузить и проанализировать браузер его должен полностью.
Предположим у нас есть веб-приложение с 10 страницами, из которых пользователи используют обычно 2-3, а к остальным обращаются редко, а то и вообще никогда. Будет странно загружать весь код (для всех страниц) одним куском (файлом).
Для решения этого вопроса и придумали разделение кода на небольшие кусочки (или чанки). В результате браузер загружает, анализирует и выполняет только тот код, который действительно нужен.
Разделение кода — не (совсем) автоматический процесс и требует осознанного подхода. Обычно, в отдельный чанк выносится та часть кода (со всеми её зависимостями), которая была использована через динамический импорт:
Но то, как именно код будет разделён, в конечном счёте зависит от настроек инструментов сборки, которые вы используете.
Как именно делить код, вопрос индивидуальный и зависит от конкретной задачи. Однако можно выделить пару универсальных советов. Во-первых, старайтесь выносить каждую страницу в отдельный чанк. Во-вторых, выносите функционал который не доступен на странице по умолчанию, без дополнительных действий пользователя.
Кстати, если вы пишите приложение с использованием React, то встроенная функция lazy и компонент Suspense должны быть вам хорошо знакомы.
#frontend #development #codesplitting #javascript #react
Начать нужно с того, что весь (frontend) код который мы пишем, в конечном итоге загружается пользователями. Помимо самой загрузки, браузеру нужно этот код прочитать, проанализировать и выполнить. На всё это, как ни странно, нужно время, и чем больше этого кода, тем больше времени будут занимать все эти процессы.
Стоит сделать важное замечание: даже если нужно выполнить какую-то небольшую часть кода, загрузить и проанализировать браузер его должен полностью.
Предположим у нас есть веб-приложение с 10 страницами, из которых пользователи используют обычно 2-3, а к остальным обращаются редко, а то и вообще никогда. Будет странно загружать весь код (для всех страниц) одним куском (файлом).
Для решения этого вопроса и придумали разделение кода на небольшие кусочки (или чанки). В результате браузер загружает, анализирует и выполняет только тот код, который действительно нужен.
Разделение кода — не (совсем) автоматический процесс и требует осознанного подхода. Обычно, в отдельный чанк выносится та часть кода (со всеми её зависимостями), которая была использована через динамический импорт:
import('./something')
Но то, как именно код будет разделён, в конечном счёте зависит от настроек инструментов сборки, которые вы используете.
Как именно делить код, вопрос индивидуальный и зависит от конкретной задачи. Однако можно выделить пару универсальных советов. Во-первых, старайтесь выносить каждую страницу в отдельный чанк. Во-вторых, выносите функционал который не доступен на странице по умолчанию, без дополнительных действий пользователя.
Кстати, если вы пишите приложение с использованием React, то встроенная функция lazy и компонент Suspense должны быть вам хорошо знакомы.
#frontend #development #codesplitting #javascript #react
Архитектурная ошибка модальных окон в React
Один из примеров того, как не нужно делать в React.
Есть некий компонент в котором через меню открывается одно из модальных окон:
Каждое из этих модальных окон это форма которой необходимы некоторые данные с сервера. React выполняет все эти компоненты, и как следствие, загружает все необходимые им данные разом. Получаем лишнюю нагрузку на браузер клиента и на сервер.
Дополнительной проблемой является то, что при закрытии такой формы, её состояние не сбрасывается, т.к. компонент продолжает “существовать”.
Решается это через условный render, ну или использованием функции по типу:
Плюсом, можно обернуть каждый компонент в lazy. В таком случае, по “запросу” будут загружаться не только необходимые данные, но и сам код компонента.
#react #frontend #development #architecture
Один из примеров того, как не нужно делать в React.
Есть некий компонент в котором через меню открывается одно из модальных окон:
function Comp() {
return (
<>
<Menu /* выбор какое окно открыть */ />
<ModalA isOpen={modal === 'a'} />
<ModalB isOpen={modal === 'b'} />
<ModalC isOpen={modal === 'c'} />
</>
);
}
Каждое из этих модальных окон это форма которой необходимы некоторые данные с сервера. React выполняет все эти компоненты, и как следствие, загружает все необходимые им данные разом. Получаем лишнюю нагрузку на браузер клиента и на сервер.
Дополнительной проблемой является то, что при закрытии такой формы, её состояние не сбрасывается, т.к. компонент продолжает “существовать”.
Решается это через условный render, ну или использованием функции по типу:
ui.openModal(<ModalA />)
Плюсом, можно обернуть каждый компонент в lazy. В таком случае, по “запросу” будут загружаться не только необходимые данные, но и сам код компонента.
#react #frontend #development #architecture
Паттерн switch(true)
Интересный паттерн который однако встречается достаточно редко. Настолько, что его полноценная поддержка в typescript появилась только в версии 5.3 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#switch-true-narrowing
Если в коде видим большое количество else if условий, например:
Возможно, это то самое место, где можно и нужно применить switch(true):
Конечно это субъективно, но на мой взгляд, так выглядит понятнее и самое главное логичнее.
Кстати, в SolidJS есть компоненты которые используют этот паттерн:
#javascript #typescript #frontend #development #patterns #react #solidjs
Интересный паттерн который однако встречается достаточно редко. Настолько, что его полноценная поддержка в typescript появилась только в версии 5.3 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#switch-true-narrowing
Если в коде видим большое количество else if условий, например:
if (/* condition #1 */) {
// code #1
} else if (/* condition #2 */) {
// code #2
} else if (/* condition #3 */) {
// code #3
} else {
// code #4
}
Возможно, это то самое место, где можно и нужно применить switch(true):
switch(true) {
case /* condition #1 */:
// code #1
break;
case /* condition #2 */:
// code #2
break;
case /* condition #3 */:
// code #3
break;
default:
// code #4
}
Конечно это субъективно, но на мой взгляд, так выглядит понятнее и самое главное логичнее.
Кстати, в SolidJS есть компоненты которые используют этот паттерн:
<Switch fallback={<div>Not Found</div>}>
<Match when={state.route === "home"}>
<Home />
</Match>
<Match when={state.route === "settings"}>
<Settings />
</Match>
</Switch>
#javascript #typescript #frontend #development #patterns #react #solidjs
www.typescriptlang.org
Documentation - TypeScript 5.3
TypeScript 5.3 Release Notes
Если вы вдруг хотели посмотреть исходный код реального коммерческого проекта на SolidJS, то вот вам ссылка: https://github.com/spsaucier/clearspend-ui
#solidjs #frontend #development #sourcecode
#solidjs #frontend #development #sourcecode
GitHub
GitHub - spsaucier/clearspend-ui
Contribute to spsaucier/clearspend-ui development by creating an account on GitHub.
👾1
Типичная ошибка использования .sort()
Даже опытные разработчики могут не увидеть ошибку в следующем коде:
А она заключается в том, что метод sort не создаёт нового массива, а изменяет текущий. И это не очевидное поведение может привезти к появлению ошибок. Решается это довольно просто:
Видимо это всех настолько “утомило”, что в спецификацию добавили метод toSorted.
Ещё одним “решением” для предотвращения появления этой ошибки в typescript является использование ReadonlyArray. Но, к сожалению, про него практически ни кто не знает.
#frontend #development #javascript #typescript
Даже опытные разработчики могут не увидеть ошибку в следующем коде:
function sortItems<T>(items: T[]): T[] {
return items.sort(/* some sort function */);
}
А она заключается в том, что метод sort не создаёт нового массива, а изменяет текущий. И это не очевидное поведение может привезти к появлению ошибок. Решается это довольно просто:
function sortItems<T>(items: T[]): T[] {
return [...items].sort(/* some sort function */);
}
Видимо это всех настолько “утомило”, что в спецификацию добавили метод toSorted.
Ещё одним “решением” для предотвращения появления этой ошибки в typescript является использование ReadonlyArray. Но, к сожалению, про него практически ни кто не знает.
#frontend #development #javascript #typescript
https://transform.tools — большой набор всевозможных конвертеров для разработчиков.
Например SVG > JSX или JSON > TypeScript.
#frontend #development #devtools
Например SVG > JSX или JSON > TypeScript.
#frontend #development #devtools
transform.tools
A polyglot web converter that's going to save you a lot of time.
Пример улучшения функции сортировки
Сортировка — довольно распространённый вид операции с данными в JavaScript / TypeScript. Например, в одном из рабочих проектов .sort встречается 97 раз в 65 файлах. При этом важно, чтобы этот код был максимально читаемым и компактным.
Обратите внимание, что некоторые примеры, для упрощения восприятия, будут без типизации. В конце будет ссылка на рабочий TypeScript код целиком.
Рассмотрим следующий пример:
На первый взгляд, код как код, и вроде бы даже всё предельно понятно. Но давайте попробуем его улучшить. Для начала, вынесем две общие функции.
Для сортировки по алфавиту:
Для сортировки по полю с перечислением (в нашем примере это type) в заданном порядке в виде массива (почему именно массив, объясню в конце):
Для объединения нескольких функций сортировки в один метод .sort() напишем ещё одну вспомогательную функцию:
Осталось переписать наш пример используя эти функции:
В итоге мы получили:
Несколько общих функций, которые можно повторно использовать в остальных частях нашего приложения, и тем самым сократили кодовую базу, а также улучшили читаемость.
Более удобную, с точки зрения поддержки, настройку сортировки по типу, т.к. в случае изменения списка FeatureType не нужно будет обновлять индексы.
Код полностью: https://gist.github.com/SanichKotikov/c8c3dac1f0de4b722b13c4af49b29c61
#frontend #development #javascript #typescript #sort
Сортировка — довольно распространённый вид операции с данными в JavaScript / TypeScript. Например, в одном из рабочих проектов .sort встречается 97 раз в 65 файлах. При этом важно, чтобы этот код был максимально читаемым и компактным.
Обратите внимание, что некоторые примеры, для упрощения восприятия, будут без типизации. В конце будет ссылка на рабочий TypeScript код целиком.
Рассмотрим следующий пример:
const MAP_TYPE_TO_ORDER: Record<FeatureType, number> = {
[FeatureType.Default]: 0,
[FeatureType.Local]: 1,
[FeatureType.Unknown]: 2,
};
function sortFeaturesByTypeAndTitle(features: Feature[]): Feature[] {
return [...features].sort((a, b) => {
const numA = MAP_TYPE_TO_ORDER[a.type];
const numB = MAP_TYPE_TO_ORDER[b.type];
if (numA !== numB) {
return numA - numB;
}
return a.title.localeCompare(b.title);
});
}
На первый взгляд, код как код, и вроде бы даже всё предельно понятно. Но давайте попробуем его улучшить. Для начала, вынесем две общие функции.
Для сортировки по алфавиту:
function inAbcOrderBy(prop) {
return (a, b) => a[prop].localeCompare(b[prop]);
}
Для сортировки по полю с перечислением (в нашем примере это type) в заданном порядке в виде массива (почему именно массив, объясню в конце):
function inCustomOrderBy(prop, order) {
return (a, b) => order.indexOf(a[prop]) - order.indexOf(b[prop]);
}
Для объединения нескольких функций сортировки в один метод .sort() напишем ещё одну вспомогательную функцию:
function inPriority(...sorters) {
return (a, b) => {
for (let i = 0; i < sorters.length; i++) {
const result = sorters[i](a, b);
if (result !== 0) return result;
}
return 0;
};
}
Осталось переписать наш пример используя эти функции:
const TYPE_ORDER = [
FeatureType.Default,
FeatureType.Local,
FeatureType.Unknown,
];
function sortFeaturesByTypeAndTitle(features: Feature[]): Feature[] {
return [...features].sort(
inPriority(
inCustomOrderBy('type', TYPE_ORDER),
inAbcOrderBy('title')
)
);
}
В итоге мы получили:
Несколько общих функций, которые можно повторно использовать в остальных частях нашего приложения, и тем самым сократили кодовую базу, а также улучшили читаемость.
Более удобную, с точки зрения поддержки, настройку сортировки по типу, т.к. в случае изменения списка FeatureType не нужно будет обновлять индексы.
Код полностью: https://gist.github.com/SanichKotikov/c8c3dac1f0de4b722b13c4af49b29c61
#frontend #development #javascript #typescript #sort
Gist
sort-example.ts
GitHub Gist: instantly share code, notes, and snippets.
Разделение кода (на чанки) в Webpack
Не так давно я писал зачем нужно разделять код на чанки, а теперь давайте рассмотрим некоторые нюансы настройки Webpack, связанные с этим вопросом.
Для начала, создадим папку src и добавим туда три файла, index.js и два других (предположим foo.js и baz.js), динамически импортируемых в первый. Затем добавим минимальный файл конфигурации:
Кстати, когда речь идёт о долгосрочном проекте, рекомендую всегда самим настраивать сборку, а не использовать готовые решения. Так ваш файл конфигурации будет максимально компактным, будет меньше случайных ошибок, ну и плюсом вы будете отлично понимать для чего в нём каждая строчка.
Запускаем сборку командой
Также видим что файлы минифицированны, так как мы указали
Собираем проект ещё раз (теперь без минификации) и видим что помимо нашего кода в файлах (в папке `dist`) содержится дополнительный сервисный код. В динамически загружаемых файлах он выглядит примерно так:
А вот в main.js его побольше, но оно и понятно, всё же это основной файл. Нас же интересует карта, в которой описано какой чанк (по chunk id) в каком файле лежит. Выглядит это примерно вот так:
Из всего этого понимаем, что каждому файлу присваивает некоторый chunk id. И вот тут давайте остановимся поподробнее. Если почитать документацию, то окажется что можно настраивать то, как именно это id будет формироваться. Но почему это важно? Дело в том, что код проекта может доставляться на сервер по несколько раз в сутки, но при этом меняться должно только то что мы действительно поменяли, что бы браузер пользователя не скачивал тот же самый код заново. Резонный вопрос — а разве может поменяться то, что мы не меняли? Да, может.
Если у вас всё ещё Webpack 4, то по умолчанию эти id — обычные порядковые номера. В итоге, может получиться так что вы поменяли одну строку исходного кода, а по факту изменилось половина файлов (в папке `dist`), потому что изменились id. Однако, начиная с 5 версии это поведение изменили (https://webpack.js.org/blog/2020-10-10-webpack-5-release/#deterministic-chunk-module-ids-and-export-names), и теперь эти id не меняются между сборками.
Есть ещё один момент, почему файлы могут измениться.
Давайте вернёмся к нашему примеру и изменим исходный код в файле foo.js. Собираем проект, и видим что поменялся не только foo.js, но и main.js. Всё дело в той самой карте, т.к. обновился foo.js, соответственно обновился и его contenthash, а он записывается в main.js. И этот момент тоже можно настроить:
Благодаря чему весь сервисный код, в том числе и карта, будут собираться в отдельный небольшой файл (чанк). Соответственно, теперь при изменении какого-то файла в исходном коде, в сборке поменяется только файл, где этот код лежит и runtime чанк.
#devtools #webpack #frontend #development #optimization
Не так давно я писал зачем нужно разделять код на чанки, а теперь давайте рассмотрим некоторые нюансы настройки Webpack, связанные с этим вопросом.
Для начала, создадим папку src и добавим туда три файла, index.js и два других (предположим foo.js и baz.js), динамически импортируемых в первый. Затем добавим минимальный файл конфигурации:
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
// про contenthash как нибудь в другой раз
filename: '[name].[contenthash].js',
},
};
Кстати, когда речь идёт о долгосрочном проекте, рекомендую всегда самим настраивать сборку, а не использовать готовые решения. Так ваш файл конфигурации будет максимально компактным, будет меньше случайных ошибок, ну и плюсом вы будете отлично понимать для чего в нём каждая строчка.
Запускаем сборку командой
webpack --mode=production и видим папку dist в которой появилось три файла: main.js, foo.js и baz.js. Из чего понимаем, что некоторая настройка разделения кода в Webpack по умолчанию уже есть (на самом деле их там много).Также видим что файлы минифицированны, так как мы указали
--mode=production. Давайте, на время изучения, выключим эту оптимизацию что-бы лучше понять во что Webpack собирает наш код.
module.exports = {
/* ... */
optimization: {
minimize: false,
},
};
Собираем проект ещё раз (теперь без минификации) и видим что помимо нашего кода в файлах (в папке `dist`) содержится дополнительный сервисный код. В динамически загружаемых файлах он выглядит примерно так:
(self["..."] = self["..."] || [])
.push([[/* chunk id */], {
[/* module id */]: (() => {
/* код модуля */
})
}]);
А вот в main.js его побольше, но оно и понятно, всё же это основной файл. Нас же интересует карта, в которой описано какой чанк (по chunk id) в каком файле лежит. Выглядит это примерно вот так:
return "" +
{ "386": "baz", "957": "foo" }[chunkId] + "." +
{ "386": "22...33", "957": "cf...d0" }[chunkId] +
".js";
Из всего этого понимаем, что каждому файлу присваивает некоторый chunk id. И вот тут давайте остановимся поподробнее. Если почитать документацию, то окажется что можно настраивать то, как именно это id будет формироваться. Но почему это важно? Дело в том, что код проекта может доставляться на сервер по несколько раз в сутки, но при этом меняться должно только то что мы действительно поменяли, что бы браузер пользователя не скачивал тот же самый код заново. Резонный вопрос — а разве может поменяться то, что мы не меняли? Да, может.
Если у вас всё ещё Webpack 4, то по умолчанию эти id — обычные порядковые номера. В итоге, может получиться так что вы поменяли одну строку исходного кода, а по факту изменилось половина файлов (в папке `dist`), потому что изменились id. Однако, начиная с 5 версии это поведение изменили (https://webpack.js.org/blog/2020-10-10-webpack-5-release/#deterministic-chunk-module-ids-and-export-names), и теперь эти id не меняются между сборками.
Есть ещё один момент, почему файлы могут измениться.
Давайте вернёмся к нашему примеру и изменим исходный код в файле foo.js. Собираем проект, и видим что поменялся не только foo.js, но и main.js. Всё дело в той самой карте, т.к. обновился foo.js, соответственно обновился и его contenthash, а он записывается в main.js. И этот момент тоже можно настроить:
optimization: {
/* ... */
runtimeChunk: true,
},
Благодаря чему весь сервисный код, в том числе и карта, будут собираться в отдельный небольшой файл (чанк). Соответственно, теперь при изменении какого-то файла в исходном коде, в сборке поменяется только файл, где этот код лежит и runtime чанк.
#devtools #webpack #frontend #development #optimization
Разделение кода (на чанки) в Webpack (Выводы):
⁃ Включите
⁃ Если вы поменяли значение
⁃ Если вы всё ещё собираете проект с помощью Webpack 4, то пора переходить на 5.
⁃ Если перейти на 5 ну ни как не получается — добавьте HashedModuleIdsPlugin (https://v4.webpack.js.org/plugins/hashed-module-ids-plugin/).
⁃ Если у вас Webpack 5, проверьте что вы не используете HashedModuleIdsPlugin (если конечно не было веской причины его добавить).
#devtools #webpack #frontend #development #optimization
⁃ Включите
runtimeChunk.⁃ Если вы поменяли значение
optimization.chunkIds или moduleIds — проверьте чтобы значение соответствовало deterministic.⁃ Если вы всё ещё собираете проект с помощью Webpack 4, то пора переходить на 5.
⁃ Если перейти на 5 ну ни как не получается — добавьте HashedModuleIdsPlugin (https://v4.webpack.js.org/plugins/hashed-module-ids-plugin/).
⁃ Если у вас Webpack 5, проверьте что вы не используете HashedModuleIdsPlugin (если конечно не было веской причины его добавить).
#devtools #webpack #frontend #development #optimization
В продолжение темы "разделение кода в Webpack", сравним с Vite
Реальный пример production кода (после сборки):
app.js — основной код приложения;
client.js — страница работы с клиентом;
subscription.js — всплывающее окно подписки клиента (открывается со страницы клиента).
Приложение загружается всегда, а вот страница и модальное окно асинхронно (по требованию) отдельными файлами.
Соберём исходный код проекта с помощью Webpack и Vite, с настройками по умолчанию, и сравним результаты сборки после внесения изменений в код модального окна (subscription.js).
Webpack:
Изменения будут только в двух файлах, subscription.js (очевидно) и app.js, потому что там находится карта всех чанков. Примечание: если у вас включен runtime chunk, то вместо app.js изменённая карта будет в нём. Подробнее про карту и runtime я писал ранее.
Vite:
Изменения будут во всей цепочке: в subscription.js (очевидно), в client.js потому что именно из него открывается модальное окно, и в app.js потому что из него открывается страница. Так происходит, потому что Vite использует стандартный JavaScript modules (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) и прописывает путь и название файла непосредственно в то место где оно используется.
#devtools #webpack #frontend #development
Реальный пример production кода (после сборки):
app.js — основной код приложения;
client.js — страница работы с клиентом;
subscription.js — всплывающее окно подписки клиента (открывается со страницы клиента).
Приложение загружается всегда, а вот страница и модальное окно асинхронно (по требованию) отдельными файлами.
Соберём исходный код проекта с помощью Webpack и Vite, с настройками по умолчанию, и сравним результаты сборки после внесения изменений в код модального окна (subscription.js).
Webpack:
Изменения будут только в двух файлах, subscription.js (очевидно) и app.js, потому что там находится карта всех чанков. Примечание: если у вас включен runtime chunk, то вместо app.js изменённая карта будет в нём. Подробнее про карту и runtime я писал ранее.
Vite:
Изменения будут во всей цепочке: в subscription.js (очевидно), в client.js потому что именно из него открывается модальное окно, и в app.js потому что из него открывается страница. Так происходит, потому что Vite использует стандартный JavaScript modules (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) и прописывает путь и название файла непосредственно в то место где оно используется.
#devtools #webpack #frontend #development
Автоматическая генерация TypeScript типов API, помогает или нет?
Рано или поздно, на проекте может возникнуть предложение генерировать типы API автоматически, например из свагера. При этом, обычно приводятся следующие плюсы:
- Типы всегда соответствуют актуальному API.
- Не нужно писать типы руками.
На практике же, обычно это превращается в следующее:
- Файл(ы) с типами со временем становятся очень большими, и редакторы начинают не слабо так подвисать при работе с ними.
- Типы действительно соответствуют API, вот только 100% соответствующего реальности API я почти ни когда не видел.
- Генераторы имеют свойство переставать работать в самый не подходящий момент. На решения этих проблем приходится тратить дополнительное время.
- Автоматическое обновление не получается сделать полностью автоматическим, например из-за проблемы выше, поэтому файл(ы) с типами могут не слабо так устаревать. Доходит до обновления типов отдельной задачей.
В итоге вместо экономии времени и соответствия типов между фронтом и беком, получаем ровно противоположный эффект. Мало того, что вы потратите время на настройку и внедрение этой системы, так ещё придётся периодически её донастраивать и решать появляющиеся проблемы.
При этом, ни кто не говорит что типы нужно писать руками, есть куда более простые решения не требующие настройки и внедрения, например внешний инструмент (https://transform.tools/json-to-typescript) который сформирует типы на основе json примера.
Если уж и настраивать генератор типов в проекте, стоит хотя бы позаботиться о том, чтобы типы генерировались в отдельные файлы (папки), и только те которые нужны для конкретной задачи, а не все подряд.
Рано или поздно, на проекте может возникнуть предложение генерировать типы API автоматически, например из свагера. При этом, обычно приводятся следующие плюсы:
- Типы всегда соответствуют актуальному API.
- Не нужно писать типы руками.
На практике же, обычно это превращается в следующее:
- Файл(ы) с типами со временем становятся очень большими, и редакторы начинают не слабо так подвисать при работе с ними.
- Типы действительно соответствуют API, вот только 100% соответствующего реальности API я почти ни когда не видел.
- Генераторы имеют свойство переставать работать в самый не подходящий момент. На решения этих проблем приходится тратить дополнительное время.
- Автоматическое обновление не получается сделать полностью автоматическим, например из-за проблемы выше, поэтому файл(ы) с типами могут не слабо так устаревать. Доходит до обновления типов отдельной задачей.
В итоге вместо экономии времени и соответствия типов между фронтом и беком, получаем ровно противоположный эффект. Мало того, что вы потратите время на настройку и внедрение этой системы, так ещё придётся периодически её донастраивать и решать появляющиеся проблемы.
При этом, ни кто не говорит что типы нужно писать руками, есть куда более простые решения не требующие настройки и внедрения, например внешний инструмент (https://transform.tools/json-to-typescript) который сформирует типы на основе json примера.
Если уж и настраивать генератор типов в проекте, стоит хотя бы позаботиться о том, чтобы типы генерировались в отдельные файлы (папки), и только те которые нужны для конкретной задачи, а не все подряд.
transform.tools
JSON to TypeScript
An online playground to convert JSON to TypeScript
Демо-проект с реализацией SRP (Secure Remote Password) + подпись последующих запросов:
Исходный код: https://github.com/SanichKotikov/srp-demo
А тут можно поиграться: https://sanichkotikov.github.io/srp-demo/
> Протокол SRP позволяет пользователю идентифицировать себя на сервере, при этом не передавая своего пароля, то есть подтвердить тот факт, что он знает свой пароль, и только этот факт.
#security #frontend #sourcecode #development #demo
Исходный код: https://github.com/SanichKotikov/srp-demo
А тут можно поиграться: https://sanichkotikov.github.io/srp-demo/
> Протокол SRP позволяет пользователю идентифицировать себя на сервере, при этом не передавая своего пароля, то есть подтвердить тот факт, что он знает свой пароль, и только этот факт.
#security #frontend #sourcecode #development #demo
GitHub
GitHub - SanichKotikov/srp-demo: Secure Remote Password (SRP) demo, using typescript, crypto and bigint.
Secure Remote Password (SRP) demo, using typescript, crypto and bigint. - SanichKotikov/srp-demo
Странная ошибка ERR_UPLOAD_FILE_CHANGED в Chrome при воспроизведении аудио
Столкнулся с такой ситуацией, что у некоторых пользователей при попытке воспроизвести аудио возникает ошибка ERR_UPLOAD_FILE_CHANGED. При этом, повторит её локально не получается, в инкогнито она не воспроизводится, какой-то закономерности обнаружить не удалось, в общем магия…
Подопытный код:
Меняем на:
И чудесным образом всё начинает работать.
#frontend #development
Столкнулся с такой ситуацией, что у некоторых пользователей при попытке воспроизвести аудио возникает ошибка ERR_UPLOAD_FILE_CHANGED. При этом, повторит её локально не получается, в инкогнито она не воспроизводится, какой-то закономерности обнаружить не удалось, в общем магия…
Подопытный код:
const blob = await fetch(src).then((resp) => resp.blob());
audio.src = URL.createObjectURL(blob);
audio.play();
Меняем на:
const { type, file } = await fetch(src).then((resp) => (
resp.arrayBuffer().then((file) => ({
type: resp.headers.get('content-type'),
file,
}))
));
const blob = new Blob([file], { type: type || void 0 });
audio.src = URL.createObjectURL(blob);
audio.play();
И чудесным образом всё начинает работать.
#frontend #development
Про технические собеседования
Технические собеседования на позицию фронтенд разработчика, что с ними не так и как это исправить. Обычно, тех. интервью состоит из двух этапов: теория и “лайф-кодинг”. Давайте по порядку:
Теория
Важно понимать кого и на какую позицию вы собеседуете. Одно дело начинающего, и совсем другое старшего разработчика или вообще тех. лида. Но и в том и другом случае, это должно быть именно СО-беседование, т.е. двух-стороннее общение, а не допрос или экзамен как это обычно бывает. Да, схемы типа “мы тебя сейчас поспрашиваем, а потом в конце ты задашь свои вопросы” не тянут на общение.
Самое худшее что вы можете сделать, это просто сухо пройтись по вопросам, начиная от того что такое замыкание и прототипное наследование, до каких-то специфических кейсов библиотеки которую вы используете (например, React). Если для начинающих такая схема ещё терпима, то для разработчиков с опытом 10+ лет это просто плевок в лицо. Например, от просьб рассказать в каком порядке выполнятся console.log в коде с тайм-аутами и промисами разного вида, хочется сказать “удачи в поисках” и прекратить пустую трату времени.
Вместо этого необходимо расспросить кандидата про его опыт, в чём по его мнению он силён, а в чём не очень, углубиться в сильные стороны, и вскользь затронуть слабые. Узнать какие крутые штуки он делал. Не для галочки, как это обычно происходит, а с дополнительными вопросами о реализации, что и почему было сделано именно так, с какими проблемами столкнулся и т.д. Идеально, если вы вспомните похожую задачу в вашем проекте и обсудите это. Задача — узнать, как именно кандидат может дополнить команду и насколько подходит на позицию.
Опять же, не забываем про уровень. Начинающий вряд ли успел поделать что-то интересное, нужно это проговорить, и попросить рассказать про типовые задачи, может быть что-то сложное было и т.д. Если у кандидата есть open source или пет-проекты, хорошо бы посмотреть их заранее и обсудить по возможности. Если кандидат имеет большой опыт, бОльший упор стоит делать на архитектуру, как бы он построил новое приложение, какие библиотеки выбрал бы и почему.
Расскажите какие у вас сейчас задачи, что бы хотели изменить, спросите мнение кандидата, как бы он подошёл к решению (естественно, при наличии соответствующего опыта).
Лайф-кодинг
Как по мне, этот этап ни каким образом не даст вам представление о том как именно кандидат думает и пишет код. Никто и никогда не пишет код в браузере в незнакомом редакторе без автодополнений, без интернета, да ещё и когда на него смотрят “следователи” и ждут что он это всё как-то будет комментировать. Даже очень опытные, в такой ситуации, могут забыть про элементарные вещи и выглядеть глупо.
Просить решать абстрактные задачки на алгоритмы, когда ваши обычные задачи это “красить кнопочки” и “клепать формы” — тоже максимально не эффективный подход. И да, не льстите себе, у вас именно такие задачи, ну если вы не пишете очередной клон ворда или экселя.
Лучше подготовьте небольшой абстрактный (или реальный) код с какими-то ошибками и попросите провести ревью этого кода, заодно поговорите про ревью в целом, зачем оно, как его проводить, на что обращать внимание и т.п.
Если у кандидата есть GitHub, посмотрите код там.
За сим, хороших вам собеседований и отличных вакансий.
#interview #frontend #development
Технические собеседования на позицию фронтенд разработчика, что с ними не так и как это исправить. Обычно, тех. интервью состоит из двух этапов: теория и “лайф-кодинг”. Давайте по порядку:
Теория
Важно понимать кого и на какую позицию вы собеседуете. Одно дело начинающего, и совсем другое старшего разработчика или вообще тех. лида. Но и в том и другом случае, это должно быть именно СО-беседование, т.е. двух-стороннее общение, а не допрос или экзамен как это обычно бывает. Да, схемы типа “мы тебя сейчас поспрашиваем, а потом в конце ты задашь свои вопросы” не тянут на общение.
Самое худшее что вы можете сделать, это просто сухо пройтись по вопросам, начиная от того что такое замыкание и прототипное наследование, до каких-то специфических кейсов библиотеки которую вы используете (например, React). Если для начинающих такая схема ещё терпима, то для разработчиков с опытом 10+ лет это просто плевок в лицо. Например, от просьб рассказать в каком порядке выполнятся console.log в коде с тайм-аутами и промисами разного вида, хочется сказать “удачи в поисках” и прекратить пустую трату времени.
Вместо этого необходимо расспросить кандидата про его опыт, в чём по его мнению он силён, а в чём не очень, углубиться в сильные стороны, и вскользь затронуть слабые. Узнать какие крутые штуки он делал. Не для галочки, как это обычно происходит, а с дополнительными вопросами о реализации, что и почему было сделано именно так, с какими проблемами столкнулся и т.д. Идеально, если вы вспомните похожую задачу в вашем проекте и обсудите это. Задача — узнать, как именно кандидат может дополнить команду и насколько подходит на позицию.
Опять же, не забываем про уровень. Начинающий вряд ли успел поделать что-то интересное, нужно это проговорить, и попросить рассказать про типовые задачи, может быть что-то сложное было и т.д. Если у кандидата есть open source или пет-проекты, хорошо бы посмотреть их заранее и обсудить по возможности. Если кандидат имеет большой опыт, бОльший упор стоит делать на архитектуру, как бы он построил новое приложение, какие библиотеки выбрал бы и почему.
Расскажите какие у вас сейчас задачи, что бы хотели изменить, спросите мнение кандидата, как бы он подошёл к решению (естественно, при наличии соответствующего опыта).
Лайф-кодинг
Как по мне, этот этап ни каким образом не даст вам представление о том как именно кандидат думает и пишет код. Никто и никогда не пишет код в браузере в незнакомом редакторе без автодополнений, без интернета, да ещё и когда на него смотрят “следователи” и ждут что он это всё как-то будет комментировать. Даже очень опытные, в такой ситуации, могут забыть про элементарные вещи и выглядеть глупо.
Просить решать абстрактные задачки на алгоритмы, когда ваши обычные задачи это “красить кнопочки” и “клепать формы” — тоже максимально не эффективный подход. И да, не льстите себе, у вас именно такие задачи, ну если вы не пишете очередной клон ворда или экселя.
Лучше подготовьте небольшой абстрактный (или реальный) код с какими-то ошибками и попросите провести ревью этого кода, заодно поговорите про ревью в целом, зачем оно, как его проводить, на что обращать внимание и т.п.
Если у кандидата есть GitHub, посмотрите код там.
За сим, хороших вам собеседований и отличных вакансий.
#interview #frontend #development
👍5
Предупреждение для тех, кто хочет перейти с Webpack на Vite
Небольшой пост-предупреждение для тех, кто (вдруг) задумывается перейти с Webpack на Vite и у кого не крохотный проект, а что-то более комплексное.
Если совсем коротко, не делайте этого!
Если чуть подробнее, то:
- Нормальных инструментов анализа бандла нет, Statoscope вы будете видеть только в своих влажных снах;
- Нормальной настройки, чтобы в лучшем случае обновлялся хеш одного (последнего) чанка, а не всей цепочки до него, нет (писал про это);
- Чтобы нормально настроить разделение чанков вам придётся пострадать, но скорее всего у вас вообще ничего не получится. Про вендор чанк авторы вообще написали: думайте сами, но так уж и быть вот вам deprecated плагин (который работает не так, как вы можете этого ожидать);
- Нормального (единого) кода ошибки загрузки css нет, только ошибка браузера (про это тоже писал);
- Плагинов всё же маловато, и их качество и работоспособность порой сильно хромает.
П.С. Если вы думаете что Webpack медленный, то скорее всего у вас что-то не правильно настроено.
П.П.С. Если вам всё равно на бандлы, чанки, анализ и прочие страшные вещи, а 10 мб вашего великолепного Javascript кода это нормально, пользователи потерпят, то можно и Vite 😅
#frontend #development #webpack #vite
Небольшой пост-предупреждение для тех, кто (вдруг) задумывается перейти с Webpack на Vite и у кого не крохотный проект, а что-то более комплексное.
Если совсем коротко, не делайте этого!
Если чуть подробнее, то:
- Нормальных инструментов анализа бандла нет, Statoscope вы будете видеть только в своих влажных снах;
- Нормальной настройки, чтобы в лучшем случае обновлялся хеш одного (последнего) чанка, а не всей цепочки до него, нет (писал про это);
- Чтобы нормально настроить разделение чанков вам придётся пострадать, но скорее всего у вас вообще ничего не получится. Про вендор чанк авторы вообще написали: думайте сами, но так уж и быть вот вам deprecated плагин (который работает не так, как вы можете этого ожидать);
- Нормального (единого) кода ошибки загрузки css нет, только ошибка браузера (про это тоже писал);
- Плагинов всё же маловато, и их качество и работоспособность порой сильно хромает.
П.С. Если вы думаете что Webpack медленный, то скорее всего у вас что-то не правильно настроено.
П.П.С. Если вам всё равно на бандлы, чанки, анализ и прочие страшные вещи, а 10 мб вашего великолепного Javascript кода это нормально, пользователи потерпят, то можно и Vite 😅
#frontend #development #webpack #vite
[SolidJS] Suspense + createResource
Suspense в SolidJS реагирует не только на ленивую загрузку компонентов, но и на загрузку данных через встроенный createResource. На первый взгляд это может показаться очень удобно, но есть нюанс.
Возьмём для примера следующий код:
Тут всё ожидаемо: пока приложение загружает список, пользователь видит заголовок и надпись “Loading list”.
Но давайте, например, добавим отображение количество элементов списка в заголовке:
И вот теперь, вместо казалось бы ожидаемого “Loading list”, пользователь увидит “Loading app”.
С одной стороны всё логично — мы добавили чтение из list за пределы внутреннего Suspense, поэтому отработал Suspense выше. Но как мне кажется, простое добавление createMemo (или чего-то подобного) не должно менять порядок отрисовки.
#frontend #development #solidjs
Suspense в SolidJS реагирует не только на ленивую загрузку компонентов, но и на загрузку данных через встроенный createResource. На первый взгляд это может показаться очень удобно, но есть нюанс.
Возьмём для примера следующий код:
<Suspense fallback={<div>Loading app...</div>}>
<List />
</Suspense>
const List: Component = () => {
const [list] = createResource(fetchList);
return (
<section>
<header>
<h4>Title</h4>
</header>
<Suspense fallback={<div>Loading list...</div>}>
<ul>
<For each={list()}>
{(item) => <li>Item #{item}</li>}
</For>
</ul>
</Suspense>
</section>
);
};
Тут всё ожидаемо: пока приложение загружает список, пользователь видит заголовок и надпись “Loading list”.
Но давайте, например, добавим отображение количество элементов списка в заголовке:
const List: Component = () => {
const [list] = createResource(fetchList);
const count = createMemo(() => {
return list()?.length;
});
return (
<section>
<header>
<h4>Title <span>{count()}</span></h4>
</header>
<Suspense fallback={<div>Loading list...</div>}>
<ul>
<For each={list()}>
{(item) => <li>Item #{item}</li>}
</For>
</ul>
</Suspense>
</section>
);
};
И вот теперь, вместо казалось бы ожидаемого “Loading list”, пользователь увидит “Loading app”.
С одной стороны всё логично — мы добавили чтение из list за пределы внутреннего Suspense, поэтому отработал Suspense выше. Но как мне кажется, простое добавление createMemo (или чего-то подобного) не должно менять порядок отрисовки.
#frontend #development #solidjs
👍1
Что не так с React
Вот уже почти 10 лет я использую React, как для коммерческой разработки, так и для персональной. Из них 4 года, параллельно, использую и SolidJS. И как известно, именно в сравнении начинаешь лучше понимать плюсы и минусы обеих сторон.
Почему именно SolidJS, а не Angular, Vue или что-то другое? Всё просто — верхнеуровнево они очень похожи. Я бы даже сказал что SolidJS умышленно скопировал JSX компоненты и “хуки”, но это лишь на уровне идеи, работает же всё иначе.
Что же не так с React?
Количество выполнений тела функции-компонента (и всего содержимого по цепочке вниз). Бороться с лишними выполнениями несложно, но мало кто хочет тратить на это время. В реальности почти ни кто не хочет думать нужно ли в текущем компоненте применять memo, useMemo, useCallback, как часто будут заново создаваться массивы и объекты, как правильно инициализировать какой-нибудь класс и т.п. Зачастую это приводит к проблемам производительности в больших приложениях, и порой достаточно серьёзным. А на решение этих проблем приходится тратить дополнительное время.
Необходимость (правильно) указывать зависимости хуков. Даже правило для линтера придумали специальное… которое иногда больше мешает, чем помогает. А реализацию сложного эффекта (или вычисления), который должен зависеть от одной переменной, но при этом не должен зависеть от пары других, всегда имея их актуальные значения, кроме как через костыль в виде useRef, кажется, и не решить. В общем, опять требуется дополнительные временные затраты. Ах да, ещё вы точно не обойдёте срача на ревью по поводу нужен в каком-то конкретном месте useMemo или нет.
Виртуальный DOM. Да да, то что когда-то было преимуществом, теперь становится недостатком (в контексте веб-разработки). SolidJS прекрасно обходится без него, при этом меняя в реальном DOM-е только то, что нужно, благодаря системе сигналов. Нет VDOM-а — нет дополнительных вычислений и потребления памяти.
Большой вес и отсутствие tree shaking. За столько лет в библиотеку много чего было написано, и хотите вы того или нет, всё это попадёт в вашу сборку.
Тем не менее это не делает React плохим, а Solid хорошим, у всего есть свои плюсы и минусы. Но всё же, если есть такая возможность, я выберу именно SolidJS.
#frontend #development #react #solidjs
Вот уже почти 10 лет я использую React, как для коммерческой разработки, так и для персональной. Из них 4 года, параллельно, использую и SolidJS. И как известно, именно в сравнении начинаешь лучше понимать плюсы и минусы обеих сторон.
Почему именно SolidJS, а не Angular, Vue или что-то другое? Всё просто — верхнеуровнево они очень похожи. Я бы даже сказал что SolidJS умышленно скопировал JSX компоненты и “хуки”, но это лишь на уровне идеи, работает же всё иначе.
Что же не так с React?
Количество выполнений тела функции-компонента (и всего содержимого по цепочке вниз). Бороться с лишними выполнениями несложно, но мало кто хочет тратить на это время. В реальности почти ни кто не хочет думать нужно ли в текущем компоненте применять memo, useMemo, useCallback, как часто будут заново создаваться массивы и объекты, как правильно инициализировать какой-нибудь класс и т.п. Зачастую это приводит к проблемам производительности в больших приложениях, и порой достаточно серьёзным. А на решение этих проблем приходится тратить дополнительное время.
Необходимость (правильно) указывать зависимости хуков. Даже правило для линтера придумали специальное… которое иногда больше мешает, чем помогает. А реализацию сложного эффекта (или вычисления), который должен зависеть от одной переменной, но при этом не должен зависеть от пары других, всегда имея их актуальные значения, кроме как через костыль в виде useRef, кажется, и не решить. В общем, опять требуется дополнительные временные затраты. Ах да, ещё вы точно не обойдёте срача на ревью по поводу нужен в каком-то конкретном месте useMemo или нет.
Виртуальный DOM. Да да, то что когда-то было преимуществом, теперь становится недостатком (в контексте веб-разработки). SolidJS прекрасно обходится без него, при этом меняя в реальном DOM-е только то, что нужно, благодаря системе сигналов. Нет VDOM-а — нет дополнительных вычислений и потребления памяти.
Большой вес и отсутствие tree shaking. За столько лет в библиотеку много чего было написано, и хотите вы того или нет, всё это попадёт в вашу сборку.
Тем не менее это не делает React плохим, а Solid хорошим, у всего есть свои плюсы и минусы. Но всё же, если есть такая возможность, я выберу именно SolidJS.
#frontend #development #react #solidjs
👍1