theme | layout | image | class | highlighter |
---|---|---|---|---|
seriph |
image-right |
text-center |
shiki |
Kottans TypeScript
layout: image-right image: https://dm1files.storage.live.com/y4mCoQWpJOraz62DCi1-WE75N4jA7Yl9wYGPoRAVY_7CdB7RnngpKcaI1k2aZkmUuOE2N5GSYpptlDtaM_FH6jh5m0zHgwXQpAT16j4qh_vIQ56teD_r8MLUs3jYAPWvco_Kg9t4ju9lAFz73tE3ICKFWmqSUqpmKNMDH8qY3BMsS6oCUdE6MgNUZilA1zF58Ph?width=3492&height=4656&cropmode=none
💻 Team Lead в Wix
- 😼 Community lead в Kottans
- 💼 Бывший юрист
- 🏔 Люблю горы. Два раза был в Непале на высотах 5100 и 5400
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
В целом об инструментах которые есть в TS для создания своих типов
Обо всех типах и операторах, edgecases, usecases
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Так что давайте сделаем жизнь друг-друга немного удобнее
- Что-то неясно? Можно и нужно перебить и задать вопрос
- Вопросы задавать лучше голосом - все хотят их услышать и понять, что "не я один не отстреливаю, что мне тут говорят"
- Кто не задает вопросы тот
<подставьте свое определение>
- Немного про JavaScript
- Немного о том, в кого TS "такой" и какой "такой"
- Немного о том почему нам надо писать свои типы и почему без этого никуда
- И это все чтобы подвести к Utility Types
- И отправлю читать доку
layout: image-right image: https://am3pap006files.storage.live.com/y4mTBVrNzHtdMrvw6jDsU-hJCfsmoaWiqaW3CFPCn8J8ZjuoyWT7GlMAPvHB25KmHwIDT3RAmlhvhdcuWPoGk3KkVFuBgVe6hCLLilvZBDhlOIE8IJ7eJj9WYDGLwDUsFPjYGdPodek7HicHmNeRgIFvfMPqcRyupsKglFBblYUJNjNxPrQjtWOkJENQuv_m9Oc?width=2529&height=2529&cropmode=none
- Расчитывает на то, что у вас будет база
- Расскажет как пользоваться Utility Types о которых расскажу я
- Еще более углубленно покажет то, как они работают
- И если все будет ок - решит пару интересных задач
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
- Basic types -
number
,string
, etc. interface
vstype
- Union and intersection types -
|
и&
const users = [
{ name: 'Anastasiya', superPower: 'VJUH!!!', isKottan: true },
{ name: 'Elon Mask', suprePower: 'Smart', isKottan: false },
{ name: 'Khrystyna', superPower: 'Charming', isKottan: true },
{ name: 'Kottan', superPower: 'knowledge sharing', isKottan: 'absolutely!' },
{ name: 'Oleksiy', superPower: 'Management', isKottan: true },
];
function findByPower(users, superPower) {
return users.find(user => user.superPower === superPower);
}
const kottans = users.filter(user => user.isKottan === true);
const smartPerson = findByPower(users, 'Smart')
const hasMagician = Boolean(findByPower(users, 'VJUH!!!').length);
Но в целом, как написали - так и работает
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Для этого он должен быть прощающим и гибким
falsy
иtruthy
значения- конвертация типов -
==
,===
,!!
- на старте только простые ментальные модели - никаких промисов!!!
- и т.п.
Время шло, задачи и проблемы становились сложнее:
- нехватка стандартной библиотеки решается другими библиотеками -
mootools
,jQuery
,lodash
- необходимость управления сложной логикой - фреймворками
Backbone
,AngularJS
,React
и т.п. - а часть других проблем может решиться только типами -
TypeScript
иFlow
TypeScript работает по принципу Types First - не смотрит на сам код, а смотрит в основном только на типы
const elem = document.getElementById('#my-id'); // null | Element
if (elem !== null) {
return
}
elem.classList.add('classname') // Element may be null
TS все равно выводил ошибку несмотря на наличие проверки. Сейчас уже не так из-за наличия type guard
и control flow analysis
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Flow пытался понимать ход исполнения программы и быть "типизацией без необходимости написания типов". Но не срослось - долгое время проверки, частые false positive ошибки, отстутствие документации и типов для сторонних библиотек. Сейчас говорят получше стало
Будет пытаться додумывать, там где это возможно
function sum(a: number, b: number): number {
return a + b;
}
const result = sum(1, 2);
vs
function sum(a: number, b: number) {
return String(a) + b;
}
const result = sum(1, 2); // typeof result === 'string'
A где невозможно - молча вставит any
и не будет проверять 👌
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
JavaScript сам по себе натура ветренная, динамичная и на этапе проектирования в нем не подразумевалась декларация типов.
Проверку типов хотели встроить в ES3 (???) версии, но не стали дабы не усложнять
Соответственно было целое сообщество разработчиков, которое типы не видело в глаза, но кода написало на весь интернет.
Соответственно, в отличии от шарпов, джавы и т.п., он может накатываться вокруг существующего проекта в котором типизации нет, все работает на вжухах и принципе "я знаю, что оно именно так работает".
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style><style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
Я расскажу:
Pick<Type, Keys>
vsOmit<Type, Keys>
Exclude<Type, ExcludeUnion>
vsExtract<Type, Union>
ThisType<Type>
, NonNullable<Type>
, Parameters<Type>
, ConstructoParameters<Type>
, InstanceType<Type>
, ThisParameterType<Type>
, OmitThisParameter<Type>
, Uppercase<StringType>
, ReturnType
, etc.
- Generics 101
Record<Keys, Type>
typeof
vskeyof
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
const pick = (source: Record<string, any>, keys: Array<string>) => {
const result = []
keys.forEach(key => {
if (key in source) {
result.push(source[key])
}
})
return result
}
const source = {
kottan: 'mascot',
anastasiya: 'VJUH!!!',
nikita: 'framework-power'
}
const result = pick(source, ['nikita', 'anastasiya']) // ???
const exclude = (source: Array<string>, toExclude: Array<string>) => {
return source.filter(item => !toExclude.includes(item))
}
const source = ['nikita', 'anastasiya', 'mascot']
const result = exclude(source, ['nikita', 'anastasiya']) // ???
Вам страшно?
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Дженерики получают аргументы и возвращают результат
interface ISource {
kottan: 'mascot';
anastasiya: 'VJUH!!!';
nikita: 'framework-power';
}
type Picked = Pick<ISource, 'nikita' | 'anastasiya'> // { nikita: 'framework-power'; anastasiya: 'VJUH!!!' }
type Omitted = Omit<ISource, 'nikita' | 'anastasiya'> // ???
В начале не понял, а потом как понял 🙀
type Source = 'nikita' | 'anastasiya' | 'mascot'
type Excluded = Exclude<Source, 'nikita' | 'anastasiya'> // 'mascot'
type Extracted = Extract<Source, 'nikita' | 'anastasiya'> // ???
Все! Вы знаете дженерики! Можно идти проситься на работу синьора-помидора! 💸
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
type IsString<TArgument> = TArgument extends string ? TArgument : never;
type MaybeString = IsString<'string'>
type MaybeString2 = IsString<true>
В этом примере использованы:
extends
- значение должно расширять тип строкиconditional type
- типа тернарный оператор (шутка за 300)never
- означает, что мы не хотим работать с результатами этих вычислений
type IsStringOrNumber<TArgument> = TArgument extends string
? TArgument
: TArgument extends number
? TArgument
: never;
type MaybeNumber = IsStringOrNumber<1>;
Есть идеи?
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>const sum = (a: number, b: number) => { ... };
type Sum<Ta extends number, Tb extends number> = ...;
Sum<'1', '2'> // Error!
const sum = (a: number = 2, b: number = 2) => { ... };
type Sum<Ta extends number = 2, Tb extends number = 2> = ...;
type Result = Sum; // Maybe4 :D
Вроде ничего сложного?
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Пример на самом деле немного кривой - не надо использовать конкретные значения в типах
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
TypeScript, исходя из типа указанного в декларации функции, понимает какие типы аргументов может принимать ваша функция
function isString<TMaybeString>(maybeString: TMaybeString) {
return typeof maybeString === 'string';
}
const stringType = isString('1'); // function isString<string>(maybeString: string): boolean
const numberType = isString(2); // function isString<number>(maybeString: number): boolean
Можно указать тип по умолчанию:
function isString<TMaybeString extends number>(maybeString: TMaybeString) ...
const stringType = isString('1'); // Болбес? Ошибка же
Вопрос с подвохом - как указать тип значения по умолчанию для аргумента?
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>const unpacked = variable => {
if (typeof variable === 'string') {
return 'string';
}
if (Array.isArray(variable)) {
if (onlyStrings(variable)) {
return 'string';
}
if (onlyNumbers(variable)) {
return 'number';
}
}
if (typeof variable === 'function') {
return typeof variable()
}
return typeof variable;
};
Тепло...
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>type T0 = Unpacked<string>; // string
type T1 = Unpacked<Array<string>>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Array<Promise<string>>>; // Promise<string>
type T5 = Unpacked<Unpacked<Array<Promise<string>>>>; // string
Горячее...
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>type Unpacked<T> = T extends Array<string>
? string
: T extends Array<number>
? number
: T extends Promise<string>
? string
: T extends Promise<number>
? number
: T;
Удобно так писать?
type T0 = Unpacked<string>; // string
type T1 = Unpacked<Array<string>>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Array<Promise<string>>>; // Promise<string>
type T5 = Unpacked<Unpacked<Array<Promise<string>>>>; // string
type Unpacked<T> = T extends Array<infer U>
? U
: T extends (...args: Array<any>) => infer U
? U
: T extends Promise<infer U>
? U
: T;
Давайте подумаем 🤔
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>type Unpacked<T> = T extends Array<infer U> // Если это массив - верни тип значения в массиве
? U
: T extends (...args: Array<any>) => infer U // Если это функция - верни тип того, что она вернет
? U
: T extends Promise<infer U> // ???
? U
: T;
Первая строка говорит следующее:
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Если аргумент дженерика Т является расширением массива
<выгрызи тип из массива и запиши его в переменную U>
, верни U
infer
- что-то вродеtypeof variable
, только для типов- объявляет переменную и кладет в нее ее тип
- какую переменную туда класть и все такое - он "условно" решает сам
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Если аргумент дженерика Т является расширением массива
<выгрызи тип из массива и запиши его в переменную U>
, верни U
type Unpacked<T> = T extends Array<infer U>
? U
: T extends (...args: Array<any>) => infer U
? U
: T extends Promise<infer U>
? U
: T;
type T0 = Unpacked<string>; // string
type T1 = Unpacked<Array<string>>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Array<Promise<string>>>; // Promise<string>
type T5 = Unpacked<Unpacked<Array<Promise<string>>>>; // string
IN-FER! УЗ-НАЙ ТИП САМ!
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>interface IMyRecord {
key1: number;
key2: number;
key3: number;
}
vs
type MyKeys = 'key1' | 'key2' | 'key3'
interface IMyRecord {
[key: MyKeys]: number;
}
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
Oldy, but goody
declare function fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
Новички на ринге:
declare
Promise
?
Обработаем событие 🙂
interface DocumentEventMap {
"scroll": MouseEvent;
"click": MouseEvent;
"change": Event;
// ...
}
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
Новички на ринге:
keyof
type Source = ???
const source1: Source = {
firstProperty: 4,
name: 'source',
title: 'Record'
}
const source2: Source = {
secondProperty: 4,
name: 'source',
title: 'Record'
}
type Source = {
firstProperty?: number;
secondProperty?: number;
name: string;
title: string;
}
const source1: Source = {
firstProperty: 4,
name: 'source',
title: 'Record'
}
const source2: Source = {
secondProperty: 4,
name: 'source',
title: 'Record'
}
type Source = {
firstProperty: number;
name: string;
title: string;
} | {
secondProperty: number;
name: string;
title: string;
}
const source1: Source = {
firstProperty: 4
}
const source2: Source = {
secondProperty: 4
}
type LeaveOnlyPrimitives = ???;
type StrippedMouseEvent = LeaveOnlyPrimitives<MouseEvent>;
// Dirty hack for learning purposes
const someStrippedMouseEvent = {} as StrippedMouseEvent;
someStrippedMouseEvent.initEvent(); // This expression is not callable. Type 'never' has no call signatures.
Что будем использовать - Union type, Mapped type, Conditional type, Generic, extends
, never
, as
.
Жаль infer
нет 🤪
type LeaveOnlyPrimitives<TEvent> = {};
type StrippedMouseEvent = LeaveOnlyPrimitives<MouseEvent>;
// Dirty hack for learning purposes
const someStrippedMouseEvent = {} as StrippedMouseEvent;
someStrippedMouseEvent.initEvent(); // This expression is not callable. Type 'never' has no call signatures.
Что дальше?
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Расширения типа - extends
type LeaveOnlyPrimitives<TEvent extends Event> = {};
type StrippedMouseEvent = LeaveOnlyPrimitives<MouseEvent>;
// Dirty hack for learning purposes
const someStrippedMouseEvent = {} as StrippedMouseEvent;
someStrippedMouseEvent.initEvent(); // This expression is not callable. Type 'never' has no call signatures.
Что дальше?
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Наше событие должно иметь те же свойства и значения, что и оригинал
type LeaveOnlyPrimitives<TEvent extends Event> = {
[Property in keyof TEvent]: TEvent[Property]
};
type StrippedMouseEvent = LeaveOnlyPrimitives<MouseEvent>;
// Dirty hack for learning purposes
const someStrippedMouseEvent = {} as StrippedMouseEvent;
someStrippedMouseEvent.initEvent(); // This expression is not callable. Type 'never' has no call signatures.
Правильно?
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Неправильно! Только примитивы
type LeaveOnlyPrimitives<TEvent extends Event> = {
[Property in keyof TEvent]: TEvent[Property] extends string
? TEvent[Property]
: never
};
type StrippedMouseEvent = LeaveOnlyPrimitives<MouseEvent>;
// Dirty hack for learning purposes
const someStrippedMouseEvent = {} as StrippedMouseEvent;
someStrippedMouseEvent.initEvent(); // This expression is not callable. Type 'never' has no call signatures.
Это все?
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>Ну и все, в принципе 😊
type Primitives = number | string | boolean;
type LeaveOnlyPrimitives<TEvent extends Event> = {
[Property in keyof TEvent]: TEvent[Property] extends Primitives
? TEvent[Property]
: never;
};
type StrippedMouseEvent = LeaveOnlyPrimitives<MouseEvent>;
// Dirty hack for learning purposes
const someStrippedMouseEvent = {} as StrippedMouseEvent;
someStrippedMouseEvent.initEvent(); // This expression is not callable. Type 'never' has no call signatures.
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>
Медитируйте на ссылки:
В начале лекции я говорил, что нужно знать для следующей - вы должны это знать.
Ну и все остальное о чем я тут говорил...
Оооммм... 🧘
<style> h1 { background-color: #2B90B6; background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%); background-size: 100%; -webkit-background-clip: text; -moz-background-clip: text; -webkit-text-fill-color: transparent; -moz-text-fill-color: transparent; } </style>