Skip to content

Commit

Permalink
refactor & feat: add ts-behavoir and change folder structure
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew committed Sep 3, 2023
1 parent 544e65d commit 47cdaa8
Show file tree
Hide file tree
Showing 47 changed files with 266 additions and 0 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
33 changes: 33 additions & 0 deletions src/ts/ts-interesting-behavior/Covariant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable prefer-const */
declare let b: string
declare let c: string | number

c = b // ✅

/**
* string is a sub-type of string | number, all elements of string appear in string | number,
* so we can assign b to c. c still behaves as we originally intended it. This is co-variance.
*/

/**
* This on the other hand, won’t work:
*/

type Fun<X> = (...args: X[]) => void

declare let f: Fun<string>
declare let g: Fun<string | number>

g = f // 💥 this cannot be assigned

/**
* And if you think about it, this is also clear.
* When assigning f to g, we suddenly can’t call g with numbers anymore! We miss part of the contract of g.
* This is contra-variance, and it effectively works like an intersection.
*/

/**
* This is what happens when we put contra-variant positions in a conditional type: TypeScript creates an intersection out of it.
* Meaning that since we infer from a function argument, TypeScript knows that we have to fulfill the complete contract.
* Creating an intersection of all constituents in the union.
*/
24 changes: 24 additions & 0 deletions src/ts/ts-interesting-behavior/DistributiveConditional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Distributive Conditional Types
*/

/**
* When conditional types act on a generic type, they become distributive when given a union type. For example, take the following:
*/

type ToArray<Type> = Type extends any ? Type[] : never
type StrArrOrNumArr = ToArray<string | number> // string[] | number[]
/**
* What happens here is that StrArrOrNumArr distributes on:
* 1. string | number
* and maps over each member type of the union, to what is effectively:
* 2. ToArray<string> | ToArray<number>;
* 3. string[] | number[]
*/

/**
* To avoid this default behavior surround each side of the extends keyword with square brackets
*/

type _ToArray<Type> = [Type] extends any ? Type[] : never
type _StrArrOrNumArr = _ToArray<string | number> // (string | number)[]
126 changes: 126 additions & 0 deletions src/ts/ts-interesting-behavior/IntersectionToUnion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
export {}

type _UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (
x: infer R
) => any
? R
: never

interface Format320 {
urls: { format320p: string }
}
interface Format480 {
urls: { format480p: string }
}
interface Format720 {
urls: { format720p: string }
}
interface Format1080 {
urls: { format1080p: string }
}

interface BasicVideoData {
id: number
}

type Video = BasicVideoData & (Format320 | Format480 | Format720 | Format1080)

const video1: Video = {
id: 20,
urls: {
format320p: 'https://...',
},
}

// FormatKeys = never, but why?
/**
* Since the Video type is a combination of all these types,
* the urls key from each of the four types is being overridden by the next one,
* so the final object will have only one key urls, but its value can be any of the four possible values
* { format320p: string }, { format480p: string }, { format720p: string }, or { format1080p: string },
* but never all of them, so the Video["urls"] will have the type of never
*/

type FormatKeysFromUnion = keyof Video['urls'] // never
type FormatKeysFromIntersection = keyof _UnionToIntersection<Video['urls']> // format320p | format480p | format 720p | format 1080p







/**
* MUCH MORE DETAILED DESCRIPTION STEP BY STEP
*/


type UnionToIntersection<T> =
(T extends any ? (x: T) => any : never) extends
(x: infer R) => any ? R : never

type Intersected1 = UnionToIntersection<Video["urls"]>

// equals to

type Intersected2 = UnionToIntersection<
{ format320p: string } |
{ format480p: string } |
{ format720p: string } |
{ format1080p: string }
>

// we have a naked type, this means we can do
// a union of conditionals:

type Intersected3 =
UnionToIntersection<{ format320p: string }> |
UnionToIntersection<{ format480p: string }> |
UnionToIntersection<{ format720p: string }> |
UnionToIntersection<{ format1080p: string }>

// expand it...

type Intersected4 =
({ format320p: string } extends any ?
(x: { format320p: string }) => any : never) extends
(x: infer R) => any ? R : never |
({ format480p: string } extends any ?
(x: { format480p: string }) => any : never) extends
(x: infer R) => any ? R : never |
({ format720p: string } extends any ?
(x: { format720p: string }) => any : never) extends
(x: infer R) => any ? R : never |
({ format1080p: string } extends any ?
(x: { format1080p: string }) => any : never) extends
(x: infer R) => any ? R : never

// conditional one!

type Intersected5 =
(x: { format320p: string }) => any extends
(x: infer R) => any ? R : never |
(x: { format480p: string }) => any extends
(x: infer R) => any ? R : never |
(x: { format720p: string }) => any extends
(x: infer R) => any ? R : never |
(x: { format1080p: string }) => any extends
(x: infer R) => any ? R : never

// conditional two!, inferring R!
type Intersected6 =
{ format320p: string } |
{ format480p: string } |
{ format720p: string } |
{ format1080p: string }

// But wait! `R` is inferred from a contra-variant position
// I have to make an intersection, otherwise I lose type compatibility

type Intersected7 =
{ format320p: string } &
{ format480p: string } &
{ format720p: string } &
{ format1080p: string }
56 changes: 56 additions & 0 deletions src/ts/ts-interesting-behavior/NakedVsNotNaked.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// type Naked<T> =
// T extends ... // naked!

/**
* Naked types in conditional types have a certain feature.
* If T is a union, they run the conditional type for each constituent of the union.
* So with a naked type, a conditional of union types becomes a union of conditional types. (A | B)[] => A[] | B[]
*/

// type NotNaked<T> =
// { o: T } extends ... // not naked!

type WrapNaked<T> = T extends any ? { o: T } : never

type Foo1 = WrapNaked<string | number | boolean> // { o: string; } | { o: number; } | { o: false; } | { o: true; }

// A naked type, so this equals to

type Foo2 = WrapNaked<string> | WrapNaked<number> | WrapNaked<boolean>

// equals to

type Foo3 = string extends any
? { o: string }
: never | number extends any
? { o: number }
: never | boolean extends any
? { o: boolean }
: never

type Foo4 = { o: string } | { o: number } | { o: boolean }

// Not-naked version

type WrapNotNaked<T> = { o: T } extends any ? { o: T } : never
type WrapNakedModifying<T> = [T] extends [any] ? { o: T } : never

type Foo5 = WrapNotNaked<string | number | boolean> // { o: string | number | boolean; }
type Foo51 = WrapNakedModifying<string | number | boolean> // { o: string | number | boolean; }

// A non Naked type, so this equals to

type Foo6 = { o: string | number | boolean } extends any
? { o: string | number | boolean }
: never

type Foo7 = { o: string | number | boolean }

/**
* Shortly speaking:
* if T is union - naked type distributes union to subtypes and maps over each of them separetely
* (Transform<A | B> => Transform<A> | Transform<B> )
* if T is union - not-naked type run the conditional type for the whole union at once (?)
(Transform<A | B> => Transform<A | B>)
* */
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// declare let orderStates: OrderStates // того же поведения (1) вне фукнкции можно добиться declare - тайпскрипт не подсматривал за инициализацией )

interface OrderCreateState {
status: 'create'
}
interface OrderCanselState {
status: 'cancel'
message: string
}
type OrderStates = OrderCreateState | OrderCanselState

const orderStates: OrderStates = {
status: 'create',
}

orderStates.status = 'cancel' // ошибка, т.к. orderStates = OrderCreateState

// если так сделать, то все ок
// orderStates = {
// status: 'cancel',
// message: 'error'
// }

function cancelOrder() {
// тайпскрипт не может строго доказать что это присваивание приведет объёкт в неконсистентное состояниек (1)
orderStates.status = 'cancel' // нет ошибки
}

0 comments on commit 47cdaa8

Please sign in to comment.