Ayub Begimkulov - уроки по JS
3.11K subscribers
29 photos
212 links
По вопросам и деловым предложениям писать на @ayub_begimkulov
Download Telegram
Всем привет!

Сегодня хотел бы поделится 2-мя jsdoc тегами (это называется block tags, насколько я помню), которыми я пользуюсь в TypeScript коде.

Это теги - @deprecated и @internal. Давайте по порядку:

@deprecated:

По сути, этот тег размечает кусок кода, как устаревший. Также в редакторе у вас использование этого куска будет перечеркнуто (1-й скрин).

Для чего это может быть нужно?

В целом, тут все очевидно. Если у вас есть библиотека компонентов или большой проект, где какая-то утилита, переменная, класс или даже свойство стало не актуальным, но вы не можете его удалить сразу, так как на него завязано очень много кода, то для начала стоит его пометить, как deprecated.

Также, советую всегда писать мессадж, чтобы человек мог понять, что ему стоит использовать вместо этого.


/**
* @deprecated - no longer actual, use SOME_NEW_CONSTANT instead.
*/
export const SOME_OLD_CONSTANT = 1234;


Данный мессаджу будет показан в редакторе, при наведений на эту переменную (2-й скрин).

@internal:

На самом деле, этого тега даже нету в jsdoc спецификации (по крайней мере, я его не нашел там). Однако он работает при использовании TS.

Данный тег нужен для того, чтобы пометить какой-то кусок кода, как “внутренний”. В таком случае, он не попадет в .d.ts файлы после компиляции.

Как вы уже могли понять, используется это только для библиотек. Бывает очень полезно, когда есть какой-то пропс в компоненте, который может использоваться только внутри библиотеки, для хоков, например, или для компонентов-оберток. Но конечный пользователь должен туда ничего не передавать.

Либо же, если у вас есть утилита, которая экспортируется для тестирования, но не должно юзатся конечным пользователем.

В общем, применений тут может быть много.

P.S. Дайте знать, как вам такой формат и про что хотите услышать еще.
P.S.S. Буду помечать подобные посты тегом #devtips, чтобы было проще искать по каналу. + Буду докидывать 2-й тег по теме самого поста.

#devtips #typescript
👍11👏4🔥21💯1🍓1
Всем привет!

Быстрый совет для тех, кто юзает ts-jest для юнит тестов (обычно это почти все, кто пишут на TS и юзают jest).

Зачастую тесты начинают запускаться через какой-то значительный промежуток времени, особенно если проект большой.

Одно из решений может быть выставление isolatedModules в конфиге ts-jest. Если у вас он уже стоит в tsconfig.json, все равно стоит попробовать выставить его еще раз руками.

Был опыт того, что данный опшен не подхватывался корректно, но там был довольно странный сетап (extends + несколько конфигов для фронта и бека).

Почему это помогает ускорить сборку я расскажу в видео про опешены tsconfig, которое я буду снимать сегодня.

Хорошего всем дня!

#devtips #typescript
👍33🔥112💯1🍓1🆒1
Всем привет!

Сегодня хотел бы поделиться библиотекой для тайпскрипта, которая поможет расставить @ts-expect-error или @ts-ingore комменты в вашей кодовой базе.

В целом, особо про usage думаю рассказывать нечего, просто запустил, передал пару опшенов и в целом все.

Ок, прикольно, но когда мне это понадобиться?

Я надеюсь, что никогда…

Но если быть чуть более реалистичным, то можно понять, что многие проекты написаны далеко не идеально. Поэтому такое вполне может пригодится.

Кейсы тут думаю очевидные - нужно поменять tsconfig опшены на более строгие или обновить версию ts, где есть ломающие изменения, а проект большой.

Ингода в таких случаях бывает не вариантом пройтись по всем ошибкам и пофиксить их - слишком много времени займет.

Тогда можно просто поправить конфиг/обновить версию, пройтись этим инструментом и вуаля.

Самое главное, что новый код будет писаться уже правильно. А старые ошибки можно будет потихоньку поправить.

Например, у меня проект не был нормально переписан на TS из-за чего в файлах были ошибки и не было тайпчека в ci. Соответсвенно можно было лего накосячить и даже не понять этого.

А ошибок сумарно было 1100 в 360 файлах. Никто понятное дело на такое дело не пойдет.

Как-то так. Всем хорошего дня!

#devtips #typescript

https://github.com/kawamataryo/suppress-ts-errors
12👍9🔥1💯1🍓1
Всем привет!

Сегодня без мухлежей буду писать нормальную мини-статью.

Хочу поговорить про такую важную тему, как перевод проекта на TS.

Думаю много людей работают на проектах, где TS уже используется во всю. Однако до сих пор есть кодовые базы, которые только начали переезжать или только собираются.

Данная статья как раз для вас!

Также если у вас есть проект, который был плохо переведен, то я уже писал пост про то, как это можно исправить (ищете по ключевой фразе “suppress-ts-errors”).

Собственно, к самой теме.

В первую очередь, хотел бы задать правильный настрой, при переводе проекта.

Не нужно ожидать, что через месяц уже все ваши файлы будут переведены на TS. Это можно сделать, но стоит ли?

Ответ, надеюсь, у вас тоже нет.

Айюб, а как же тогда переписывать проект?

В моем опыте хорошо работает такой подход:

1) Добавляем в проект возможность писать TS файлы. Очень важно, tsconfig должен быть как можно более строгим с самого начала. А то потом намучаетесь его исправлять.

Единственное что, даем импортировать JS файлы, так как большая часть проекта пока на нем.

2) Пишем весь новый функционал на TS файлах.

3) Понимаем, какой функционал часто импортируется в другие файлы и меняется.

Такие куски можно начать переписывать вместе с какими-то задачами.

В целом, флоу такой. Если человек хочет заимпортировать модуль, написанный на JS он:

а) Импортирует его как есть, по возможности добавляет JSDoc, чтобы типизация в TS файле была правильная.
б) Если модуль старый и скорее всего не будет меняться, лучше вместо JSDoc написать .d.ts файл.
в) По желанию переписывает на TS. Это сделать намного проще, когда уже есть какие-то JSDoc аннотации + есть какое-то понимание того, как работать с тайпскриптом.

Такой механизм работы может и не приведет вас к полностью синему проекту, через пару недель, однако поверьте мне, процесс миграции произойдет быстрее, чем вы думаете.

По началу будет все идти медленно, так как разрабы будут только начинать знакомиться с ТС. Так же все модули будут написаны на JS, что будет давать дополнительную сложность.

Однако через некоторое время, когда у вас уже будут часто используемые модули покрыты JSDoc, станет не так сложно переписать их на TS.

Также легаси куски будут описаны в .d.ts, что позволит не трогать их и избежать ненужных проблем.

Новый код уже будет типизированный и разработчики уже поймут профит, который они получают от строгой типизации.

Тогда уже можно и начинать процесс переписывания. Ключевая вещь тут не заставлять это делать. Можно предлагать на ревью, например.

Либо сделать какое-то необязательное правило. Если тронул старые файлы, перепиши хотя бы один.

Идей тут много. Но в любом случае рано или поздно кто-то перепишет эти файлы, потому что так удобнее. Просто произойдет это не сразу.

В общем, думаю идея тут ясна. Не надо закапывать продуктовую разработку на несколько спринтов. Делайте так, чтобы вся работа шла параллельно.

#devtips #typescript
👍295🔥1👏1💯1🍓1
Всем привет!

Недавно наткнулся на интересное видео от Theo и задумался, что ведь и в правду не нужно везде пихать тайпчекинг.

Он же должен быть только в одной таске в CI. По крайней мере жесткий.

Например, очень часто я раньше ставил проверку типизации на unit и e2e тесты, хотя она там и не нужна, только замедляет запуск тестов. То же самое касается и билда.

Как часто у вас было, что в проекте появилась 1 маленькая проблема с TS и из-за этого все такси в пайплайне красные? И даже потестить код нельзя, из-за того, что забыл переменную неиспользуемую удалить.

Да, может быть полезно иметь эти ошибки в консоли во время разработки, но никогда не понимал, когда ошибки в TS блокируют компиляцию и запуск кода.

Я уже давно начал использовать отдельный конфиг для дева и для тайпчека, так как в деве, например, noUnusedLocals/noUnusedParameters - не несут никакого толка, только мешаются.

Однако после этого видео понял, что лучше для сборки и юнит/е2е тестов тоже все убрать. Оставить только ошибки в терминале/консоли для дева и отдельную typecheck таску в CI с жестким конфигом.

А что вы думаете по этому поводу? Как у вас все настроенно в проекте?

#devtips #typescript
👍203💯1🏆1🍓1
Всем привет!

После моего последнего видео многие из вас спрашивают о комментарии ^? внутри TS кода, который показывает тип значения, находящегося строкой выше над комментарием.

Данная фича по дефолту работает в TypeScript playground на их официальном сайте. Однако если вы хотите иметь такое же поведение в VSCode - нужно скачать плагин vscode-twoslash-queries.

В целом тут все работает также, как и при наведении мышкой в редакторе. Однако бывает очень удобно при дебаггинге типизации или когда объясняешь какую-нибудь тему другому человеку.

#devtips #typescript
17👍10🔥52💯1🍓1
Всем привет!

Продолжая нашу тему с TS, хочу поделится тем, почему не надо использовать опциональные параметры для колбэков.

Разберу я это на примере обертки над Array.prototype.map, так как о ней меня спрашивали под одним из моих собесов (тот, что “мидл в 19 лет”).

Что бы понять о чем я, давайте взглянем на этот кусок кода:


function map<Item, MappedItem>(
arr: Item[],
mapper: (item: Item, index: number, originalArray: Item[]) => MappedItem
) {
return arr.map(mapper);
}


Собственно, вопрос стоит в том, почему параметры index и originalArray - обязательные, ведь мы не всегда их используем?

Тут давайте попробуем чуть более детально разобрать, что мы говорим такой типизацией TypeScript'у?

Мы говорим, что в колбэк mapper обязательно должны приходить 3 параметра, а не только 1.

Соответсвенно, даже если человек их не использует, никакой ошибки не будет.


map([1, 2, 3], (item) => String(item));


Остальные 2 параметра придут, но они нам в данном случае просто не нужны. Данный код будет нормально работать в браузере или любом другом runtime. Ну и правила типизации мы никак не нарушаем.

Однако если сделать index и originalArray опциональными - мы получим ошибку.


function map<Item, MappedItem>(
arr: Item[],
mapper: (item: Item, index?: number, originalArray?: Item[]) => MappedItem
) {
return arr.map(mapper);
}

map([1, 2, 3], (item, index) => [item, index * 2]);
// ^^^^^
// 'index' is possibly 'undefined'.


Это происходит потому, что мы хотим использовать index, однако типизация map говорит о том, что он не всегда будет передан нам в колбэк. Из-за этого у нас появляется кейс когда index - undefined.

Вообще, в типизации колбэков обычно никогда не нужно использовать опциональные параметры, если же у вас конечно не какой-то странный кейс.

Об это говорится даже в официальном HandBook.

#devtips #typescript
👍44🔥211💯1🍓1
Всем привет!

Записал сейчас видео по деталям работы TS и React.

Однако хотел бы более детально поделится изменением в типизации хука useCallback, которые произошли при переходе на 18-ую версию.

Я думаю некоторые из вас могли заметить, что раньше при передаче функции в хук, аргументы по дефолту были типизированны, как any.


const cb = useCallback((arg1, arg2, arg3) => {
arg1; // any
arg2; // any
arg3; // any
}, []);


В целом, это может выглядеть, как нормальное поведение. Но проблема в том, что это так работает даже в strict моде.

Хотя, если я напишу просто функцию, результат будет вот такой:


const testFn = (arg1, arg2, arg3) => {
// ^^^^ ^^^^ ^^^^
// Parameter 'arg1' implicitly has an 'any' type.ts(7006)
};


Так вот, почему же так происходит? Чтобы понять причину, давайте посмотрим на типизацию хука:


function useCallback<T extends (...args: any[]) => any>(
callback: T,
deps: DependencyList
): T;


Происходит так из-за того, что сам useCallback имеет вот такой generic constraint T extends (...args: any[]) => any.

Соотвественно, мы говорим TS, что переданный callback будет являться подтипом (...args: any[]) => any, и если мы не описывать конкретные типы аргументов, то fallback будет происходить на any.

Более наглядный пример для такого поведения будет выглядеть вот так:


const runCb = (fn: (...args: any[]) => any) => {
return fn();
};

runCb((arg1, arg2) => {
arg1; // any
arg2; // any
});


Думаю тут вопросов о том, почему arg1 и arg2 — any нет. Когда мы описываем extends (...args: any[]) => any ситуация происходит примерно такая же.

Собственно, вот как эта проблема была решена в типизации React:


function useCallback<T extends Function>(
callback: T,
deps: DependencyList
): T;


Как видим в качестве constraint'а они воспользовались типом Function, а не (...args: any[]) => any.

Соответственно, такое ограничение не дает никакой информации компилятору для фолбэка. Поэтому TS заставит нас четко описать типы аргументов:


const cb = useCallback((arg1, arg2, arg3) => {
// ^^^^ ^^^^ ^^^^
// Parameter 'arg1' implicitly has an 'any' type.ts(7006)
}, []);


Как вы помните, я сам не советовал использовать тип Function, однако теперь я нашел ему дельное применение.

Всем хорошего вечера!

#devtips #typescript
25👍19🔥2💯2👌1🍓1
Всем привет!

Хотел бы поделиться своим мнением касательно enum’ов в Typescript.

Как вы знаете, сейчас стало модно их хейтить, причиной является то, какой output они генерируют.

Мне кажется это связано с тем, насколько они странно реализованы.

Если создать enum без значений, либо использовать числа в качестве значений enum — то сгенерируется странная структура данных, которая будет маппить не только элементы вашего enum к числам, но и числа к каждому элементу.

Наглядный пример можно увидеть вот в этом плейграунде.

Также есть еще одна странная вещь, которая называется const enums.

Это те же enum, только во время компиляции они инлайнятся и исчезают из кода.

В теории идея крута. На практике это может сильно замедлить сборку, да и кроме самого tsc никто особо с ними и не умеет работать (по крайней мере бабель точно не умел).

В основном по этим причинам и не хотят люди их использовать.

Однако, если использовать только не константные строковые enum — то они очень удобны.

Во первых, сразу одной записью генерируется значение + тип.

Во вторых, в те типы, которые ожидают ваш enum, нельзя присвоить обычную строку с таким же значением. Обязательно нужно использовать элемент енума. На языке умных людей это называется номинальностью.

В общем, в конечном итоге вам решать, юзать их или нет. Но если придерживаться этих 2-х правил — то я думаю, что они очень удобны.

#devtips #typescript
👍345💯32🍓1💊1
Всем привет!

Думаю многие из вас понимают, что писать сложные типы на TS — не самое просто занятие. Часто вижу, что люди добавляют библиотеки по типу utility-types или type-fest. Их даже часто можно увидеть в dependencies у других либ.

Однако намного реже вижу упоминание ts-toolbelt, хотя там есть очень много полезных типов, да и автор настоящий мастер в данном деле.

Я помню сам в свое время очень много чего полезного узнал просматривая код данной библиотеки. Так что всем советую.

https://github.com/millsp/ts-toolbelt/

#devtips #typescript
👍3352💯2🍓1
Пока все про using говорят, команда TS подвезла нам очень классные фичи для грядущей версии 5.2.

Декораторы, улучшение туплов, работа с методами массивов и тд.

Уже давно не припомню, чтобы в одном релизе сразу столько болей моих правили (последнее что помню — это 4.1).

https://devblogs.microsoft.com/typescript/announcing-typescript-5-2-beta/#decorator-metadata

#devtips #typescript
👍227🏆2🍓1
Одна важная вещь, которую нужно учитывать, если вы предпочитаете использовать типы вместо интерфейсов в TS - это отличие type intersection (`AType & BType`) от наследования интерфейсов.

Давайте для простоты понимания сразу взглянем на пример:

// с типами
type BaseModelType = {
test: string;
foo: string;
}

type MyModelType = {
test: {a: number};
} & BaseModelType;

type TestType = MyModelType['test'];
// ^?
// { a: number } & string;

// с интерфейсами
interface BaseModelInterface {
test: string;
foo: string;
}

interface MyModelInterface extends BaseModelInterface {
// ^^^^^^^^^^^^^^^^
// Interface 'MyModelInterface' incorrectly extends interface 'BaseModelInterface'.
// Types of property 'test' are incompatible.
// Type '{ a: number; }' is not assignable to type 'string'.ts(2430)
test: a: number;
};

type TestType1 = MyModelInterface['test'];
// ^?
// number


Как вы можете видеть, в кейсе с типами у нас нету overriding'а, то есть тип каждого из свойств тоже объединяется в единый результат, даже если они не совместимы.

В случае же с интерфейсами TS даст нам знать, что тип от которого мы наследуемся имеет другое несовместимой значение свойства, что не поведет за собой проблемы в других местах.

Решение у 2-х проблем одинаковое — использование Omit:

// с типами
type BaseModelType = {
test: string;
foo: string;
}

type MyModelType = {
test: {a: number};
} & Omit<BaseModelType, 'test'>;

type TestType = MyModelType['test'];
// ^?
// { a: number };

// с интерфейсами
interface BaseModelInterface {
test: string;
foo: string;
}

interface MyModelInterface extends Omit<BaseModelInterface, 'test'> {
test: number;
};

type TestType1 = MyModelInterface['test'];
// ^?
// number


Не говорю, что нужно использовать одно или другое (я считаю тут просто нужно договориться на уровне проекта), но наследование у интерфейсов по лучше.

#devtips #typescript
👍312👎1🍓1
Пару дней назад вышел документальный фильм про TypeScript (кажется такой формат набирает обороты). Нашел сегодня время посмотреть.

Конечно снято не так круто, как про React от Honeypot. Но все равно очень понравилось.

Особенно было интересно взглянуть на картину развития большого проекта внутри Microsoft. Какая была мотивация, продвижение в опен сорс, колаборации с другими проектами и тд.

Если есть свободное время — советую к просмотру.

https://youtu.be/U6s2pdxebSo?si=toaAlAJlf8sC2eom
🔥285👍5🏆2🍓2💊2👎1