Last active
August 30, 2021 10:07
-
-
Save serhii-lypko/a9e71cadfebc2bf77cc74c3c6dd42472 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| type Foo<T> = T extends { a: infer U, b: infer U } ? U : never; | |
| type T10 = Foo<{ a: string, b: string }>; // string | |
| type T11 = Foo<{ a: string, b: number }>; // string | number | |
| type T22 = Foo<{ a: string }>; // never | |
| /* * . * . * . * Type Systems vs Shapes * . * . * . */ | |
| /* . * . * . * . * . * . * . * . * . * . * . * . * . */ | |
| // Java и другие строго типизированные объектно ориентированные языки предполагают сравнение идентичности | |
| // типов на основании того являются ли эти типы экзеплярами базового типа с которым происходит сравнение | |
| // поскольку механника ООП в JS не является традиционной, TypeScript производит сравнение на основании | |
| // соответсвия проверяемого типа shape'у базового типа, с которым происходит сравнение и если | |
| // например некий объект содержит в себе все методы и функции, что и базовый тип, то он подходит | |
| // дальше: сравнение происходит по принципу от wide (any ~ infinity) к narrow (конкретный тип, а дальше -> never) | |
| // примерная диаграмма: any -> any[] -> string[] -> [string, string, string] -> ['abc', 'def', 'string'] -> never | |
| // this is sometimes called “duck typing” or “structural subtyping” | |
| // The idea behind structural typing is that two types are compatible if their members are compatible. | |
| /* - - - - - * - - - - - | |
| // TypeScript’s type system is erased, meaning that once your code is compiled, | |
| // there is no persisted type information in the resulting JavaScript code. | |
| - - - - - * - - - - - */ | |
| /* * . * . * . * . Interfaces . * . * . * . * . */ | |
| /* . * . * . * . * . * . * . * . * . * . * . * . */ | |
| Interface A { | |
| a: number; | |
| // опциональные типы полезны во-первых тем, что явно указывают тип для предполагаемого свойства, а во-вторых | |
| // тем, что явно указывают имя, предотвращая опечатки и т.д | |
| b?: boolean; | |
| } | |
| function makeFoo(obj: A) { ... } | |
| const fooObj = { | |
| a: 10, | |
| b: false, | |
| c: null | |
| } | |
| // вот здесь важно, что интерфейсы не предполагают "имплементацию" объектов (как в других языках), | |
| // а только описывают их shape, а так же обязательные & опциональные свойства (порядок не имеет значения) | |
| makeFoo(fooObj); // OK | |
| // в то же время | |
| makeFoo({ a: 10, b: 12, c: null }); // ERROR: ... object literal may only specify known properties, and 'c' ... | |
| // для этого можно расширить интерфейс, таким образом, чтобы в него входили так же любые свойства, но | |
| // при этом a и b обозначены явно | |
| Interface A { | |
| a: number; | |
| b?: boolean; | |
| [key: string]: any; | |
| } | |
| makeFoo({ a: 10, b: 12, c: null }); // OK | |
| // - - - - - - - Function Types - - - - - - - | |
| interface FunctionI { | |
| (src: string, value: number): void; | |
| } | |
| // поскольку функция теперь знает все о своих аргументах, то можно не указывать их тип, | |
| // а так же изменять именование параметров | |
| const foo1: FunctionI = (src, val) => { ... } | |
| // возможный вариант для function declaration | |
| let foo2: FunctionI; | |
| foo2 = function(src, val) { ... } | |
| // - - - - - - - Indexable Types - - - - - - - | |
| // здесь, создавая "словарь" с помощью связки [index: string]: number; | |
| // компилятор в дальнейшем будет ожидать заданный тип от всех последующих свойст в объекте | |
| interface NumberDictionary { | |
| [index: string]: number; | |
| length: number; | |
| name: string; // error, the type of 'name' is not a subtype of the indexer | |
| } | |
| // хотя union ОК | |
| interface NumberOrStringDictionary { | |
| [index: string]: number | string; | |
| } | |
| // - - - - - - - Object creation & type assertion - - - - - - - | |
| interface Shape { | |
| color: string; | |
| } | |
| // нельзя создать пустой объект по заданному интерфейсу с обязательными полями | |
| const figure: Shape = {}; // ERROR: Property 'color' is missing in type '{}' but required.. | |
| // или так же нельзя создать пустой объект, а затем попытаться записать туда поля | |
| let emptyFigure = {}; | |
| emptyFigure.color = "red"; // ERROR: Property 'color' does not exist on type '{}' | |
| // но возможно с использованием type assertion | |
| let blackFigure = {} as Shape; | |
| blackFigure.color = "black"; | |
| /* * . * . * . * . * . * Generics * . * . * . * . * . * . */ | |
| /* . * . * . * . * . * . * . * . * . * . * . * . * . * . */ | |
| /* - - - - - * - - - - - | |
| // The main reason to use generics in TypeScript (and any other programming language) is to | |
| // enable types (classes, types, or interfaces) to act as parameters. | |
| // It helps us reuse the same code for different types of input since the type itself is available as a parameter. | |
| // - Defining a relationship between input and output parameters types | |
| // - Stronger type checks at compile time will be available | |
| // - You can remove some unnecessary type casts | |
| // важно, что в отличие от any, T позволяет сохранить и использовать в дальнейшем информацию о типе | |
| // это крайне важно и позволяет опускать явную передачу типов при использовании дженерик функций и тд | |
| - - - - - * - - - - - */ | |
| // generic interface для функции | |
| interface FilterFunction<T = any> { | |
| (val: T): boolean; | |
| } | |
| const stringFilter: FilterFunction<string> = (val) => typeof val === "string"; | |
| // - - - - - - - Type constrainsts - - - - - - - | |
| // очень часто возникает необходимость в указании ограничений для какого-либо типа | |
| // в данном случае с Permissions задача заключалась в том, чтобы функция принимала разные объекты, | |
| // содержащие в себе permissions, а затем обновляла эти permissions в системе | |
| // и generic, сохраняя тип (в отличие от any) с использованием constraints позволяет сделать все красиво 👌 | |
| type UserPermissions = { | |
| canGetReports: boolean; | |
| canSaveData: boolean; | |
| } | |
| interface LoginResponse extends UserPermissions { | |
| userId: number; | |
| token: string; | |
| } | |
| interface User extends UserPermissions { | |
| id: number; | |
| email: string; | |
| } | |
| // необходимо убедиться, что T будет содержать искомые Permissions | |
| function savePermissions<T extends UserPermissions>(dataWithPermissions: T) { | |
| const permissionsKeys = ["canGetReports", "canSaveData"]; | |
| const permissions = pick(dataWithPermissions, permissionsKeys); | |
| // do stuff with permissions | |
| } | |
| savePermissions<LoginResponse>({ | |
| canGetReports: true, | |
| canSaveData: false, | |
| userId: 1, | |
| token: "token_123" | |
| }); | |
| // Type Parameters in Generic Constraints | |
| // для того, чтобы убедиться, что K будет включать в себя один из ключей объекта obj | |
| // инструкция keyof вернет список всех ключей, а extends запишет их как constraint | |
| // (type K1 = keyof Person; // "name" | "age" | "location") | |
| function getObjectValue<T, K extends keyof T>(obj: T, key: K) { | |
| return obj[key]; | |
| } | |
| const obj = {a: 1, b: 2}; | |
| const a1 = getObjectValue(obj, "a"); // Ok | |
| const some = getObjectValue(obj, "some"); // ERROR: Argument.. is not assignable to parameter of type '"a" | "b"'. | |
| // - - - - - - - Use cases - - - - - - - | |
| interface Shape { | |
| draw: () => void; | |
| } | |
| interface Circle extends Shape { | |
| radius: number; | |
| } | |
| // излишняя абстракция | |
| function drawShapesGeneric<T extends Shape>(shapes: T[]) { | |
| shapes.forEach(shape => shape.draw()); | |
| } | |
| // для того, чтобы выполнить отрисовку фигур, функции drawShapes | |
| // вообще не нужно знать про остальные свойства этих фигур -> необходимости в generic нет | |
| // (Правило: "you ask only what you need and return everything you can") | |
| // поэтому для имплементации drawShapes достаточно использовать base class Shape | |
| function drawShapes(shapes: Shape[]) { | |
| shapes.forEach(shape => shape.draw()); | |
| } | |
| const circle1: Circle = { | |
| draw: () => {}, | |
| radius: 12 | |
| }; | |
| drawShapes([circle1]); | |
| // пример правильного использования | |
| interface Shape { | |
| draw: () => void; | |
| isDrawn: boolean; | |
| } | |
| interface Circle extends Shape { | |
| radius: number; | |
| } | |
| // generic критически важен в ситуациях, когда важно сохранить тип на выходе функции | |
| // здесь, просто передавая в drawShapesWithUpdate массив Circle на выходе этот массив Circle | |
| // function drawShapesWithUpdate(shapes: S[]) -> return values asserts to Shape[] | |
| // в итоге был бы преобразован в массив Shape (тип был бы упрощен) | |
| // !!! generic здесь - "relationship between argument and return" !!! | |
| // и именно generic (вместе с constraints) позволяет использовать | |
| // правильный тип без его дальнейшей потери | |
| function drawShapesWithUpdate<S extends Shape>(shapes: S[]): S[] { | |
| return shapes.map(shape => { | |
| shape.draw(); | |
| shape.isDrawn = true; | |
| return shape; | |
| }); | |
| } | |
| const circles = [ | |
| { draw() {}, isDrawn: false, radius: 10 }, | |
| { draw() {}, isDrawn: false, radius: 12 }, | |
| ]; | |
| const drawnShapes = drawShapesWithUpdate(circles); | |
| function showRadiusForEachCircle(circles: Circle[]) { | |
| circles.forEach(circle => { | |
| console.log(circle.radius); | |
| }) | |
| } | |
| // иначе здесь была бы ошибка, поскольку функция ожидает именно Circle[], а не Shape[] | |
| showRadiusForEachCircle(drawnShapes); | |
| // * * * | |
| type Dict<T> = { | |
| [key: string]: T | undefined; | |
| } | |
| function mapDict<T, S>(dict: Dict<T>, fn: (val: T) => S ): Dict<S> { | |
| const out: Dict<S> = {}; | |
| const keys = Object.keys(dict); | |
| keys.forEach(key => { | |
| const item = dict[key]; | |
| if (typeof item !== undefined) { | |
| out[key] = fn(item); | |
| } | |
| }); | |
| return out; | |
| } | |
| // именно использование дженериков позволяет при использовании метода выполнять автоматический | |
| // type infence из передаваемых аргументов | |
| const res = mapDict(fileExtensions, e => `*.${e}`); | |
| /* * . * . * . * . Types & Interfaces . * . * . * . * . */ | |
| /* . * . * . * . * . * . * . * . * . * . * . * . * . * . */ | |
| // одно из основных отличий между type alias и интерфейсами заключается в том, что alias могут типизировать | |
| // примитивы и вообще любые сущности, то есть они более гибкие, в то время как интерфейсы могут описывать только | |
| // объекты, сигнатуры функций и массивы | |
| // выходит, что интерфейсы покрывают некоторое подмножество всех кейсов, которые покрываются alias, но | |
| // в то же время предоставляют для этого более мощный и гибкий API | |
| // self-referencing types dont work | |
| type NumVal = 1 | 2 | 3 | NumArray[] // error | |
| type NumArray = NumVal[]; // error | |
| // interfaces cannot extends union types | |
| type A = { | |
| a: string; | |
| } | { a: number }; | |
| interface A1 extends A {} // Error | |
| /* * . * . * . Function signature overloading . * . * . * . */ | |
| /* . * . * . * . * . * . * . * . * . * . * . * . * . * . * . */ | |
| interface HasEmail { | |
| name: string; | |
| email: string; | |
| } | |
| interface HasPhone { | |
| name: string; | |
| phone: string; | |
| } | |
| type Method = "email" | "phone" | |
| // возможные сигнатуры для функции | |
| function sendMessage(method: "email", ...people: HasEmail[]): void; | |
| function sendMessage(method: "phone", ...people: HasPhone[]): void; | |
| function sendMessage(method: Method, ...people: (HasEmail | HasPhone)[]) { | |
| people.forEach(send); | |
| } | |
| const peoples = [ | |
| { name: "Dan", phone: "123" }, | |
| { name: "Kan", phone: "321" }, | |
| ] | |
| sendMessage("phone", { name: "Dan", phone: "123" }); // OK | |
| sendMessage("email", { name: "Dan", email: "abc@abc" }); // OK | |
| sendMessage("email", { name: "Dan", phone: "123" }); // Error |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment