Будни разработчика
14.6K subscribers
1.14K photos
319 videos
7 files
1.96K links
Блог Lead JS-разработчика из Хельсинки
Автор: @bekharsky

По рекламе: https://telega.in/channels/htmlshit/card?r=GLOiHluU или https://t.me/it_adv

Чат: https://t.me/htmlshitchat

№5001017849, https://www.gosuslugi.ru/snet/679b74f8dad2d930d2eaa978
Download Telegram
#заметка дня

Как же у меня бомбит сейчас, вы бы знали. Но обо всём по порядку.

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

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

И, естественно, класс Config.ts перезаписал собой файл конфигурации config.js 🫠

Хвала небу, в PhpStorm есть локальная история файла, потому что конфигурация, естественно, исключена из Git.

Моя жопа давно так не горела. Это ну вообще как? Зачем? Кому это может быть полезно?! Ну типа, должна же быть хоть какая-то веская причина так делать?

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

#typescript #jetbrains #надмозг
#заметка дня

Что делать, если нужная вам библиотека не предоставила типы для всех публичных методов?

Ну вот такое вот архитектурное решение: метод экспортирован из модуля, а тип или интерфейс — нет?

Делать unknown или any? Копировать и переопределять с помощью as?

Ни в коем случае! Вам нужен простой советский... ReturnType: https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype

Пример использования — на иллюстрации. Ну или ещё можно так:

const createPerson = () => ({
firstName: 'John',
lastName: 'Doe'
})

type Person = ReturnType<typeof createPerson>

Не делайте ерунды, котаны. Читайте документацию.

#typescript #ts #types #бородач
#фишка дня

Кричащий банан 🍌 (да-да) принёс шикарную фишку TypeScript: как типизировать ключ объекта без каста.

Решение: заранее объявите тип переменной перебора как keyof typeof. Всё, вы великолепны.

Пруф на TS Playground.

#typescript #cast
#инструмент дня

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

А единой документации по этим типам нет!

Точнее, не было, но теперь вышел https://tsdocs.dev/

Это система поиска по существующим пакетам с типами. Она установит нужный пакет, распарсит типы и JSDoc и отобразит в удобном формате.

К слову, котаны, что вы предпочитаете по cmd- (ctrl)-click в редакторе? Прыгать к имплементации, или к типам?

Я вот — к имплементации, прыгать к типам меня раздражает.

#typescript #dts #types #doc
#тип дня

Попробую вместо тега #фишка внести что-то новое для TypeScript. Пусть будет "тип", а там посмотрим.

И сегодня на повестке дня запрет определённых ключей при передаче объекта. Например, контекста в трекинге или тех же пропсов в React.

Как правило, контекст в трекинге передаётся в функцию-хелпер, а после — дополняется какими-то переменными среды. Как-то так:


function logEvent(severity: LogSeverity = "info", context: LogContext = {}) {
log(severity, {
type: "event",
userId: "rick@ro.ll",
env: "dev",
...context,
})
}


Понятное дело, нам совсем неохота, чтобы кто-то случайно передал в контекст type, userId или env и затёр всё нафиг.

"Погоди, так разверни контекст повыше", — скажет кто-то умный.


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

Проще сразу не дать сделать странное, как минимум на этапе введения контекста. Наш вариант в итоге стал таким:

type LogContextDefaults = "type" | "userId" | "env";

type LogContext = {
[key: string]: string;
} & {
[key in LogContextDefaults]?: never;
}


Ну и конечно, ссылка на песочницу: пуньк.

Есть предложения получше, котаны? Ну кроме проверки в рантайме :)

#typescript #ts #type
#тип дня

Типом дня назначается вон тот в кожаной куртке. Да-да, о тебе говорю!

TL;DR: вычисленный тип функции с дженерик-аргументом можно сузить, декларируя тип как const.

Кроме шуток, разве тебя не бесит, что указываешь вот дженерик тип аргумента функции, производишь манипуляции над переданным объектом — а в ответ тебе вычисленный базовый тип?

Непонятно? Давай так:

function wrapNames <T>(names:T[]) {
return names.map<{name: T}>(name => ({name}));
}

const [first, second] = wrapNames(['Sergey', 'Alex'])


Какой вычисленный тип получат first и second?

Очевидно, такой:

{
name: string;
}


Но... это же довольно неудобно. А если мы схему валидируем, например? Или форму описываем. Или тесты пишем в конце-концов. Почему при заранее известном константном типе входных данных мы получаем вычисленную строку?

К счастью, начиная с TypeScript 5.0 можно объявлять дженерики как константные типы! Иначе говоря, работать с максимально узкими (narrow) типами. Это, собственно, аналог указания as const у предопределённой константы.

Использование простое:

function wrapNames <const T>(names:T[])

И на выходе тип у first и second будет такой:

{
name: "Sergey" | "Alex";
}


Песочница: пуньк.

Давайте исходя из полученной информации создадим валидатор по известной схеме данных и дальше поработаем с ним:


// initValidator
function parse<const T extends { name: string; surname: string }>(users: T[]) {
return (name: T['name']) => users.find(u => u.name === name)?.surname;
}

// validate
const getSurname = parse([
{
name: 'Joe',
surname: 'Doe',
age: 30,
},
{
name: 'John',
surname: 'Dohn'
}
]);


Определение getSurname приобретёт такой вид:

getSurname: (name: "Joe" | "John") => string | undefined


То есть, обратите внимание, не просто строку на вход, а конкретные, существующие в нужных параметрах строки.

Теперь это вполне себе напоминает удобную работу с коллекциями данных, не стыдно и форму провалидировать.

Песочница: пуньк.

Штука относительно новая, к ней ещё придётся немного привыкнуть. Но уже можно пробовать применять.

Ах да, ссылка на соответствующий PR в TypeScript: https://github.com/microsoft/TypeScript/pull/51865

#typescript #generic #const
#тип дня

Кричащий банан 🍌 (да-да) принёс шикарную фишку TypeScript: как типизировать ключ объекта без каста.

Решение: заранее объявите тип переменной перебора как keyof typeof. Всё, вы великолепны.

Пруф на TS Playground.

#typescript #cast #бородач
#тип дня

Говоришь такой коллеге: «Ты зачем столько классов насоздавал для такой простой вещи? Используй CSS-переменные».

А он приходит к тебе с картинкой выше и грустный весь.

Что мы делаем в таком случае?

Да очень просто, дополняем интерфейс CSSProperties, чтобы до реакта, наконец, дошло:


declare module 'react' {
interface CSSProperties {
[key: `--${string}`]: string | number
}
}


Пруф: песочница.

Странно, конечно, что это не из коробки всё.

#css #types #react #typescript
#тип дня

Вышел TypeScript 5.4.

А это что значит? Что у нас теперь есть служебный тип NoInfer.

Коротко, зачем он нужен: с его указанием TS больше не будет пытаться угадать тип передаваемого аргумента. Например, если не указать тип массива ["red", "yellow", "green"], TS определит его как string[] и разрешит запихнуть туда значение "blue" или, что хуже, позволить вашему коду попытаться, например, это самое значение в нём найти:


function createStreetLight<C extends string>(colors: C[], defaultColor?: C) {
// ...
}

createStreetLight(["red", "yellow", "green"], "blue");


Чтобы такого не происходило, есть два пути. Первый:

function createStreetLight<C extends string, D extends C>(colors: C[], defaultColor?: D) {
}

createStreetLight(["red", "yellow", "green"], "blue");
// ~~~~~~
// error!
// Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green" | undefined'.


Ну такое, многословно и D больше нигде в коде не используется. Вот тут и приходит на помощь NoInfer:

function createStreetLight<C extends string>(colors: C[], defaultColor?: NoInfer<C>) {
// ...
}

createStreetLight(["red", "yellow", "green"], "blue");
// ~~~~~~
// error!
// Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green" | undefined'.


Всё, тип массива определён как, практически, у константы ["red", "yellow", "green"] и передать "blue" уже не выйдет.

Естественно, улучшений и нововведений в TS 5.4 сильно больше, рекомендую прочитать заметки о выпуске: https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/

#typescript #generics
#ссылка дня

Типизирование React-компонентов, рефов и хуков может обернуться большой болью. Особенно если в первый раз. Но к счастью, у нас есть GitHub, который добрые люди используют не только как хранилище кода, но и как идеальный универсальный блог :)

Встречайте: https://github.com/typescript-cheatsheets/react

Буквально, из описания: «Cheatsheets for experienced React developers getting started with TypeScript».

Впрочем, у ребят есть и более привычная веб-версия, разбитая на секции: https://react-typescript-cheatsheet.netlify.app/

Мне, например, недавно понадобилось сделать полиморфичный компонент, который в зависимости от условий мог становиться то ссылкой, то кнопкой (какой прекрасный повод для холивара). Я не нашёл решения непосредственно здесь, зато нашёл в обсуждениях и PR. Что тоже показывает, как удобен GitHub для коллективного блога.

В общем, всем типов, котаны.

#typescript #react #бородач
#библиотека дня

— А что, если типы надо проверять в рантайме?
— Да не, херня какая-то...


Впрочем, создателям библиотеки Typia так совсем не кажется. Да и вам, в целом, казаться не должно: TypeScript, конечно, большой молодец, но типы проверяет только на этапе компиляции. И, конечно же, мы все верим в святые контракты :)

Библиотека предлагает не только проверку входящих данных, но и соответствие JSON описанию.

Есть как простые is и equals, возвращающие логическое соответствие, так и assert и assertEquals, кидающие ошибку.

Отличие is от equals в том, что is менее строгий и позволяет расширение объекта свойствами, которых нет в интерфейсе. Но описанные при этом должны соответствовать.

const input: unknown = {
id: v4(),
email: "samchon.github@gmail.com",
age: 30,
extra: "superfluous property", // extra
};

const is: boolean = typia.is<IMember>(input);const
equals: boolean = typia.equals<IMember>(input);

console.log(is, equals); // true, false


Обратили внимание? Схема не нужна!

Как-то так. Кто использовал уже?

#typescript #types
#инструмент дня

Если меня спросят, на чем я стал бы делать ранний прототип, я бы ответил Drupal. Ну или Ruby on Rails. Ну просто потому что я их знаю. На Drupal вообще мышкой можно все накликать и получить рабочий API.

Но, конечно, это уже сравнительно оторвано от реальности :) Простой CRUD aka Create-Read-Update-Delete можно собрать тысячей разных способов.

Так что стоит принести ещё один: Remult.

https://remult.dev/

Remult использует всю мощь декораторов TypeScript для описания своих т. н. сущностей, которые потом станут моделями.

import { Entity, Fields } from "remult"

@Entity("tasks", {
allowApiCrud: true
})
export class Task {
@Fields.cuid()
id = ""

@Fields.string()
title = ""

@Fields.boolean()
completed = false

@Fields.createdAt()
createdAt?: Date
}


После чего остается только зарегистрировать созданную сущность в сервере на, например, express и получить готовый CRUD API.

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

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

Горячая рекомендация, в целом.

#typescript #crud
#инструмент дня

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

А единой документации по этим типам нет!

Точнее, не было, но теперь вышел https://tsdocs.dev/

Это система поиска по существующим пакетам с типами. Она установит нужный пакет, распарсит типы и JSDoc и отобразит в удобном формате.

К слову, котаны, что вы предпочитаете по cmd- (ctrl)-click в редакторе? Прыгать к имплементации, или к типам?

Я вот — к имплементации, прыгать к типам меня раздражает.

#typescript #dts #types #doc #бородач
#тип дня

Что делать если вам нужно создать тип, параметры которого служат для передачи в библиотечную функцию? Aka тип аргументов не торчит наружу?

Не делайте это ручками, используйте служебный тип Parameters!


const createPerson = ({
firstName,
lastName
}: {
firstName: string,
lastName: string
}) => ({
firstName,
lastName
})

type CreatePersonParams = Parameters<typeof createPerson>[0];

const params: CreatePersonParams = {
firstName: 'John',
lastName: 'Doe'
};


Песочница

#typescript #utility
#тип дня

Четыре ночи провёл в деревне в глубинной Финляндии и возвращаться в привычный режим очень и очень тяжело.

Прыжки со скалы просто так не проходят.

Здесь отмечают день солнцестояния. Официальный выходной, плюс многие компании добавляют ещё один день — канун. Из города уезжают вообще все, пустота.

В общем, тип дня от Кори Хауса: как указать TypeScript, что поле принимает любое строковое значение, но при этом получить автоподсказку для наиболее часто используемых из них?

Очень просто:

type Status = 'open' | 'closed' | string & {}


Результат на иллюстрации. Песочница.

Объединение с пустым типом предотвращает попытки языкового сервера от приведения типа к строке.

На самом деле это равнозначно следующему выражению, более понятному:

type Status = 'open' | 'closed' | Omit<string, "open" | "closed">


Пользуемся!

#typescript
#новость дня

В node.js появилась экспериментальная нативная поддержка TypeScript!

Крепко же их bun приложил...

Ссылка на PR: https://github.com/nodejs/node/pull/53725

По факту происходит отбрасывание типов, поэтому средства вроде Enum и namespace не поддерживаются. Инициатива предоставления стабильного API поверх TypeScript получила название amaro и в дальнейшем планируется выделение в отдельный обновляемый модуль. Работает (кто бы сомневался) при помощи swc, собранного в WebAssembly!

Так что никаких больше ts-node!

node main.ts

...и поехали!

#node #typescript #ts
#расширение дня

Дал слабину и где-то в глубине кода указал any?

Воспользовался DefinitelyTyped, не проверив?

И при этом не используешь WebStorm?

Есть решение! Расширение для VS Code any-xray от Дана Вандеркама: https://marketplace.visualstudio.com/items?itemName=danvk.any-xray

Про DefinitelyTyped не шутка, всякое бывает: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/b0ad06b09bd5547cf6f01a8355cbcb309d3f5e24/types/find-package-json/package-json.d.ts#L449

У React в типах, кстати, тоже.

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

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

#vscode #typescript
#инструмент дня

Устал вручную типизировать ответы от API для TypeScript или любого другого типизированного языка?

Есть решение!

https://app.quicktype.io/

Фиганул туда JSON — получил нужную структуру или описание типа, даже с тайпгардами. Уютненько!

Есть расширение для VS Code: https://marketplace.visualstudio.com/items?itemName=quicktype.quicktype

Ещё один мощный инструмент в тему дня: https://transform.tools. Одним типизированием JSON не ограничивается: можно CSS в Tailwind, а можно Flow в TypeScript.

Вот, например, если кто использует Zod — конвертор типов в схему Zod: https://transform.tools/typescript-to-zod

#json #typescript #type #бородач
Media is too big
VIEW IN TELEGRAM
#новость дня

Вчерашнего дня, конечно, и многие из вас уже в курсе, но...

TypeScript переписывают на Go!

Да, если вы не знали — транслятор TypeScript всё это время был написан на самом себе, что, как вы понимаете, не делало его быстрым :)

И вот, новость: https://devblogs.microsoft.com/typescript/typescript-native-port/

Результат ошеломляет: разбор типов и сборка уже в 10 раз быстрее! Это означает, что и LSP в ваших IDE будут быстрее. И работать станет приятнее.

Очень рекомендую пойти почитать статью.

Кстати, кто же этот седой дядя на видео? А это Андерс Хейлсберг! Создатель Turbo Pascal, Delphi, C# и, внезапно, TypeScript.

И на этом видео он, в том числе, объясняет, почему не Rust. Этим вопросом задолбали уже всю команду.

И, если коротко:
1. Это портирование, а не переписывание. Они хотели оставить структуру проекта и работы над ним той же, что и была.
2. Rust, хоть и облегчает управление памятью, полностью не освобождает от связанного с этим управлением бойлерплейта. И, поскольку, структура транслятора TypeScript представляет из собой огромный набор вложенных друг в друга структур, количество бойлерплейта вышло бы неразумным.

В общем, нас ждёт дивный новый мир! И я очень рад. И выбором языка тоже.

#typescript #go
#баг дня

Как вы думаете, что обозначает тип {}?

Ну, буквально:


type b = {};


Быть может, пустой объект? Да. А может, объект с любыми свойствами? Тоже да. А может, число или строка? И опять, да. А пустые? Да! А может, булево значение? Невероятно, но тоже да!

А может, null или undefined ? Нет. Вот тут — нет. Песочница.

Поэтому смысла в использовании типов {} или Object (второе лишь псевдоним к первому) довольно мало.

Если вам нужен пустой объект — так и пишите, Record<PropertyKey, never>.

Подробнее у Мэтта Покока: https://www.totaltypescript.com/the-empty-object-type-in-typescript.

В чём же баг, спросите вы?

А баг у нас на этот раз в реализации проверки типов в WebStorm/PhpStorm. У JetBrains, короче.

Мы выводим типы из схем Zod, но для целей создания шаблона, мне потребовалось исключить некоторые свойства из проверки:


TemplateQueryDetails = Omit<
z.infer<typeof QueryDetails>, 'metadata'
>;


И мощно получил в лицо: тип ресолвился за 3 минуты на MacBook Pro M2, вешая интерфейс PhpStorm напрочь. Каждый раз.

Решение пришло откуда не ждали:


TemplateQueryDetails = Omit<
z.infer<typeof QueryDetails>, 'metadata'
> & {};


Обратите внимание на пустой тип. Обнаружил я это случайно: в первом варианте я ещё и добавлял айди шаблона, но впоследствии — отказался.

По всей видимости, система типов IntelliJ построена таким способом, что рассчитвывает конъюнкцию типов как новый тип и кеширует на месте. Но это лишь предположение.

В VS Code, кстати, проблемы не наблюдается.

После обращения в поддержку, мне было рекомендовано включить настройку Use types from server, что буквально выключает встроенные средства и использует типы от LSP. Что, собственно, мне только в плюс.

А как ваши дни проходят? 🙂

#jetbrains #typescript #lsp