dev.easy
132 subscribers
11 links
заметки full stack девелопера - находки, фишечки и боль современной web-разработки


notion blog: https://bit.ly/deveasy

github: https://github.com/z4o4z
Download Telegram
🎛️ Новинки TS 4.4

TypeScript с каждым релизом становится все круче и интереснее! В последней бета версии(4.4) ребята добавили парочку очень крутых фич (https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-beta/). Вот краткий обзор основных изменений:

Больше всего я заценил Control Flow Analysis of Aliased Conditions , благодаря этой фичи результат тайпгарда можно сохранить в переменную и дальше в коде использовать эту переменную что бы закастить тип. Если не поняли, вот пример:

interface BaseOptions {
prop1: string;
prop2: number;
}

interface ClickableOptions extends BaseOptions {
anchorLabel: string
onAnchorClick: VoidFunction
}

const Component = ({ prop1, prop2, ...props }: BaseOptions | ClickableOptions) => {
const isClickable = 'anchorLabel' in props;

if (isClickable) {
const { onAnchorClick } = props;
}

// ...

const anchorLabel = isClickable ? props.anchorLabel : null;
}


Как видите мы можем реюзать isClickable переменную в разных местах и TS не будет ругаться что в пропах нет anchorLabel и onAnchorClick , ну разве не топчик? 😎

В предыдущих версиях 'anchorLabel' in props приходилось выносить в отдельную тайпгард функцию и вызывать ее, что бы помочь TSу с типами, и это хорошо если тайпгард простой, и особо не влияет на перф. Ну или как альтернативный и не очень красивый вариант без тайпгарда, который уменьшает надежность типов - использовать ключевое слово as :

interface BaseOptions {
prop1: string;
prop2: number;
}

interface ClickableOptions extends BaseOptions {
anchorLabel: string
onAnchorClick: VoidFunction
}

const isClickable = (props: Omit<BaseOptions | ClickableOptions, keyof BaseOptions>): props is Omit<ClickableOptions, keyof BaseOptions> => 'anchorLabel' in props

// Good
const Component = ({ prop1, prop2, ...props }: BaseOptions | ClickableOptions) => {
if (isClickable(props)) {
const { onAnchorClick } = props;
}

// ...

const anchorLabel = isClickable(props) ? props.anchorLabel : null;
}

// Bad
const Component = ({ prop1, prop2, ...props }: BaseOptions | ClickableOptions) => {
const isClickable = 'anchorLabel' in props;

if (isClickable) {
const { onAnchorClick } = props as Omit<ClickableOptions, keyof BaseOptions>;
}

// ...

const anchorLabel = isClickable ? (props as Omit<ClickableOptions, keyof BaseOptions>).anchorLabel : null;


Также теперь можно использовать символы, оч удивило что раньше TS это не саппортал, и строковые паттерны в качестве ключей объектов, что очень полезно в некоторых кейсах, например затайпать data атрибуты в React'е стало намного проще:

interface Props {
value: string;

[symbols: symbol]: number;
[data: `${'data' | 'aria'}-${string}`]: string;
}

const simple: Props = { value: '' };
const symbols: Props = { value: '', [Symbol('')]: 0 };
const dataAttr: Props = { value: '', "data-opened": 'true', "aria-label": "label" };


Ошибки в catch блоке имеют тип unknown вместо any , что делает код намного безопаснее, т.к. придется делать тайпгарды если нужно достать что-то ( err.message ) из ошибки.

Ну и напоследок, появилась возможность сказать TSу что опциональные ключи в объектах являются ТОЛЬКО опциональными и туда нельзя засунуть undefined . Возможно эта фича покажется вам не такой уж и полезной, но она позволяет избежать таких проблем:

interface Entity {
a: number;
b: string;
}

const entity: Entity = {a: 10, b: '20'};
const patch: Partial<Entity> = { a: 10, b: undefined };
const updated: Entity = { ...entity, ...patch }

updated.b.toUpperCase(); // Runtime Error: Cannot read property 'toUpperCase' of undefined


С каждым новым релизом люблю TS все больше ❤️, надеюсь и вы перешли на TS и также кайфуете ✌️

Читать в ноушен: https://bit.ly/384LwZe

#typescript #news
👍2
🎹 Массивы и Таплы

В TS/JS массивы можно разделить на 2 типа, обычные массивы и таплы (tuple). На данный момент для js-движков между ними нет разницы и обрабатываются они как массивы. На практике таплы чаще всего используются в ситуациях, когда нужно вернуть несколько(2-3) значений из функции, если нужно больше, то лучше использовать объект.
Например в React'е, useState возвращает тапл со значением и сеттером.
Почему тапл лучше объекта для нескольких значений? При деструкторизации тапл проще использовать чем объект, а так же с ним меньше мест в которых можно допустить ошибку, достаточно знать лишь порядок данных. С объектом же нужно точно знать какие есть ключи и каких у них данные, а при деструкторизации нужно тупо больше кода для таких широко-используемых функций как useState :


// tuple
const [state1, setState1] = React.useState(0);
const [state2, setState2] = React.useState(false);

// vs

// object
const { val: state1, set: setState1 } = React.useState(0);
const { val: state2, set: setState2 } = React.useState(false);


Согласитесь вариант с таплом намного удобнее и читабельнее объекта. Но повторюсь, тапл хорош только для нескольких значений, желательно до 3. Иначе будет каша с кучей данных, разных типов, без названий. Держать такое в голове, также как и сапортать, оч сложно.

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


// tupples
type SimpleTuple = [string, boolean];
type NamedTuple = [isOpened: boolean, toggleOpened: VoidFunction];
type NamedLongTuple = [a: string, b: number, c: VoidFunction, d: boolean | undefined, e: unknown]; // BAD, better to use object
type TailTuple = [...string[], number];
type DestructTuple = [head: number, ...body: string[], tail: number];

// arrays
type SimpleArray = string[];
type UnionArray = Array<string | boolean>; // (string | boolean)[]


Как вы заметили, таплы в TS могут иметь неопределённую длину, а также типы элементов можно задавать с конца. Кстати, VoidFunction это алиас на () => void который идет по дефолту в dom неймспейсе, те кто пишет под брузер можете юзать :). Теперь давайте разберем на примере разницу между массивом и таплом:


const tuple: SimpleTuple = ['NAME', false];
const array: UnionArray = ['NAME', false];
const untyped = ['NAME', false];

// works fine, since TS knows that first item is a string!
tuple[0].toLowerCase();

// error: Property 'toLowerCase' does not exist on type 'string | boolean'
// since first element can be string or boolean
array[0].toLowerCase();

// same error as above
untyped[0].toLowerCase();


array[0].toLowerCase(); и untyped[0].toLowerCase(); выдадут ошибки, потому что для array мы явно указали тип всех элементов массива как string | boolean , а untyped так-же получил тип Array<string | boolean> т.к. всем массивам присвоенным в переменную без явно указанного типа TS дает тип всех его элементов, т.е. в нашем случае будет юнион string | boolean . Но есть синтаксис как подсказать TS'у что untyped это тапл, а не массив:


const untyped = ['NAME', false] as const;

untyped[0].toLowerCase(); // works fine


Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3kxH8aQ

#typescript #js
💯1🤓1