Будни разработчика
14.5K subscribers
1.38K photos
404 videos
8 files
2.31K links
Download Telegram
#тип дня

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

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
👍253
#тип дня

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

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

Пруф на TS Playground.

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

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

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

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

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


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


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

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

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

Вышел 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
👍204
#ссылка дня

Типизирование 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 #бородач
👍172
#библиотека дня

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


Впрочем, создателям библиотеки 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
🤩4
#инструмент дня

Если меня спросят, на чем я стал бы делать ранний прототип, я бы ответил 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
👍20
#инструмент дня

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

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

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

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

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

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

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

Что делать если вам нужно создать тип, параметры которого служат для передачи в библиотечную функцию? 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
👍13🤡32🤬1
#тип дня

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

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

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

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

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

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


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

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

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

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


Пользуемся!

#typescript
👍384