diff --git a/001.ts b/001.ts index 9057300..6cae0bb 100644 --- a/001.ts +++ b/001.ts @@ -1,6 +1,13 @@ // # Types 101 +// --- + +// SLIDE + +// --- + + // ## JS Primitives let _number: number = 1; @@ -13,6 +20,13 @@ let _undefined: undefined = undefined; let _null: null = null; +// --- + +// SLIDE + +// --- + + // ## Objects let _obj1: object = { a: 'value' }; // don't, any non primitive @@ -25,6 +39,13 @@ type UnknownObject = Record; let _obj6: UnknownObject = { a: 'value' }; +// --- + +// SLIDE + +// --- + + // ## Functions let _fun1: Function = (x) => x++; // don't @@ -41,6 +62,13 @@ let _callableFun5 = _fun5 as Function; _callableFun5(); +// --- + +// SLIDE + +// --- + + // ## Any, unknown, never function getA1(obj: any) { @@ -67,6 +95,13 @@ function throwError(msg: string): never { } +// --- + +// SLIDE + +// --- + + // ## Literals let _true: true = true; @@ -82,6 +117,13 @@ type HttpVerbs = LowercaseHttpVerbs | Uppercase; // see: Uppercase, Lowercase, Capitalize, Uncapitalize +// --- + +// SLIDE + +// --- + + // ## Arrays and tuples let _strArr1: string[] = ['val1', 'val2']; @@ -95,4 +137,12 @@ let _tuple2: [string, number, string] = ['val1', 2, 'val3']; let _tuple3: [string, number, string?] = ['val1', 2]; let _tuple4: [string, ...number[]] = ['val1', 2, 3, 4]; let _tuple5: [string, ...number[], string] = ['val1', 2, 3, 4, 'val5']; // TS >= 4.2 -let _tuple6: [first: string, second: string] = ['val1', 'val2']; \ No newline at end of file +let _tuple6: [first: string, second: string] = ['val1', 'val2']; + + +// --- + +// NEXT + +// --- + diff --git a/002.ts b/002.ts index a084903..3426f0c 100644 --- a/002.ts +++ b/002.ts @@ -1,6 +1,13 @@ // # Unions and Intersections +// --- + +// SLIDE + +// --- + + // ## Basic example type SingleDigitPrime = 2 | 3 | 5 | 7; @@ -13,6 +20,13 @@ type SingleDigitPrimeAndOdd = SingleDigitPrime & SingleDigitOdd; // result: 3 | 5 | 7 +// --- + +// SLIDE + +// --- + + // ## Object union // Basically an or operator @@ -39,7 +53,14 @@ function payTaxes(entity: Human | Company): void { } -// # Object intersection +// --- + +// SLIDE + +// --- + + +// ## Object intersection interface DbModel { id: number; } type ChildObject = { parentId: number; }; @@ -54,6 +75,13 @@ function addChild(parentId: number, child: DbModel): ChildDbModel { } +// --- + +// SLIDE + +// --- + + // ## Narrowing down types with intersections type Bipedal = { usesFourLegs: false; usesTwoLegs: true; }; @@ -84,6 +112,13 @@ const chicken: Bird = { }; +// --- + +// SLIDE + +// --- + + // ## Side note: optional is not the same as undefined let _obj7: { a?: string } = {}; @@ -92,6 +127,13 @@ let _obj7: { a?: string } = {}; // result: Property 'a' is missing in type '{}' but required in type '{ a: string | undefined; }'.ts(2741) +// --- + +// SLIDE + +// --- + + // ## interface extends // Basically the same as object intersections @@ -109,6 +151,13 @@ const chicken2: Bird2 = chicken; // result: An interface can only extend an object type or intersection of object types with statically known members.ts(2312) +// --- + +// SLIDE + +// --- + + // ## A "real" union of objects // Goal: shared types are required and individual ones are optional @@ -123,6 +172,13 @@ function payTaxes2(entity: LegalEntity): void { } +// --- + +// SLIDE + +// --- + + // ## Side note: JS nullish coalescing && optional chaining function old_getChildId(parent: any): number | null { @@ -139,3 +195,81 @@ function new_getChildId(parent: any): number | null { globalThis.notSureIfThisFunctionExists?.(); + +// --- + +// SLIDE + +// --- + + +// ## Function overloads + +function len(s: string): number; +function len(arr: any[]): number; + +function len(x: string | any[]) { + return x.length; +} + +len('string'); +len([1, 2, 3]); + + +// --- + +// SLIDE + +// --- + + +// ## Overloading arrow functions + +type Len2 = { + (s: string): number; + (arr: any[]): number; +} +let len2: Len2 = (x: string | any[]) => x.length; + +len2('string'); +len2([1, 2, 3]); + + +// --- + +// SLIDE + +// --- + + +// ## Overloading arrow functions v2 + +type Len3 = ((s: string) => number) & ((arr: any[]) => number); +let len3: Len3 = (x: string | any[]) => x.length; + +len3('string'); +len3([1, 2, 3]); + + +// --- + +// SLIDE + +// --- + + +// ## Side note: If you ever need to type `this` + +function socketController(this: SocketControllerContext, socket: any, data: any, ack?: () => void) { + const activeConnection = this.activeConnectionsRepository.findBySocket(socket); + // ... +} + +type SocketControllerContext = { activeConnectionsRepository: { findBySocket(socket: any): any }; }; + + +// --- + +// NEXT + +// --- diff --git a/003.ts b/003.ts index f362c75..eceea00 100644 --- a/003.ts +++ b/003.ts @@ -1,6 +1,13 @@ // # TS Language Extensions +// --- + +// SLIDE + +// --- + + // ## Type vs interface // Basically the same thing since TS 2.7 @@ -40,6 +47,13 @@ _myObject!.b // string // Please use interfaces when writing libraries +// --- + +// SLIDE + +// --- + + // ## enums enum StandardIOStream { @@ -66,6 +80,13 @@ enum Direction { Object.entries(Direction).length // 4 +// --- + +// SLIDE + +// --- + + // ## keyof, typeof interface Person { @@ -80,6 +101,13 @@ let _john = { firstName: 'John', lastName: 'Smith' } type Person2 = typeof _john; // { firstName: string; lastName: string; } +// --- + +// SLIDE + +// --- + + // ## Using typeof instead of copy paste declare function oddlySpecificDeepCopyAsync( @@ -87,9 +115,26 @@ declare function oddlySpecificDeepCopyAsync( ): Promise +// --- + +// SLIDE + +// --- + + // ## Get type from array -let _mixedArray = [1, 'something', null, 'literalString' as const] +let _mixedArray = [1, 'something', null, [1, 2]]; + +type MixedArrayElement = (typeof _mixedArray)[number]; +// type MixedArrayElement = string | number | number[] | null + + +// --- + +// SLIDE + +// --- // ## Constructing objects via keyof @@ -105,6 +150,13 @@ let _nameUseLookup: NameUseLookup = { } +// --- + +// SLIDE + +// --- + + // ## Using them on enums type StandardIOStreamName = keyof typeof StandardIOStream; @@ -121,6 +173,13 @@ const standardIOStreamDict: StandardIOStreamDict = { } +// --- + +// SLIDE + +// --- + + // ## as const let _john2 = { firstName: 'John', lastName: 'Smith' } as const; @@ -132,6 +191,13 @@ type JohnSmith = typeof _john2; // result: Cannot assign to 'firstName' because it is a read-only property.ts(2540) +// --- + +// SLIDE + +// --- + + // ## readonly interface ReadonlyPerson { diff --git a/004.tsx b/004.tsx index 2963f87..0beef52 100644 --- a/004.tsx +++ b/004.tsx @@ -1,7 +1,14 @@ -// # Generics & functions +// # Generics -// ## Generics +// --- + +// SLIDE + +// --- + + +// ## Syntax function genericFunction(arg: T): T { return arg; @@ -24,6 +31,13 @@ type GenericType = { arg: T }; let _numberObjectAsType: GenericType = _numberObject; +// --- + +// SLIDE + +// --- + + // ## Generic Constraints function deepCopyObject(obj: T): T { @@ -37,6 +51,13 @@ let _numberObjectCopy = deepCopyObject(_numberObject); // result: Argument of type 'number' is not assignable to parameter of type 'object'.ts(2345) +// --- + +// SLIDE + +// --- + + // ## How to type a class? function createObject( @@ -50,6 +71,13 @@ let _numberObject2 = createObject(GenericClass, [1]); // let _numberObject2: GenericClass +// --- + +// SLIDE + +// --- + + // ## Default values & React usage import * as React from 'react'; @@ -84,18 +112,16 @@ let Element = ({ opts1, opts2 }: { opts1: string[], opts2: number[] }) => { } -// ## Function overloads +// --- -function len(s: string): number; -function len(arr: T[]): number; +// NEXT -function len(x: string | any[]) { - return x.length; -} +// --- -len('string'); -len([1, 2, 3]); -len([1, 2, 'random string', [1, 2]]); -// function len(arr: (string | number | number[])[]): number +// ## FORMAT FIX: the _ is not working in tsx +_numberObjectAsInterface +_numberObjectAsType +_numberObjectCopy +_numberObject2 diff --git a/005.ts b/005.ts index 03d1d3f..1b7eb72 100644 --- a/005.ts +++ b/005.ts @@ -1,2 +1,164 @@ // # Conditional types + +// --- + +// SLIDE + +// --- + + +// ## Syntax + +// SomeType extends OtherType ? TrueType : FalseType; + +type FunctionIsObject = Function extends object ? true : false; +// type FunctionIsAnObject = true + + +// --- + +// SLIDE + +// --- + + +// ## Used with generics + +type ReadonlyIfObject = T extends object ? Readonly : T; + +interface MyObject { + a: number; +} +let _readonlyMyObject: ReadonlyIfObject = { a: 1 }; +// _readonlyMyObject.a = 2; +// Cannot assign to 'a' because it is a read-only property.ts(2540) + +let _notReadonlyNumber: ReadonlyIfObject = 1; +// let _notReadonlyNumber: number +_notReadonlyNumber = 2; + + +// --- + +// SLIDE + +// --- + + +// ## infer + +let _mixedArray2 = ['something', null, [1, 2]]; + +type ElementOf = T extends Array ? E : T; + +type MixedArray2Element = ElementOf; +// type MixedArray2Element = string | number[] | null + +type SomeNumber = ElementOf; +// type SomeNumber = number + +// N.B.: `infer` always goes between `extends` and `?` + + +// --- + +// SLIDE + +// --- + + +// ## recursive + +type ElementOfRecursive = T extends Array ? ElementOfRecursive : T; + +type MixedArray2ElementRecursive = ElementOfRecursive; +// type MixedArray2ElementRecursive = string | number | null + +// NOTE: Some restrictions if < 4.1 + +// FUN FACT: Recursive type aliases needed to be "hacked" before 3.7 [Nov 2019] +// 3.7 is the one that brought optional chaining and nullish operators + + +// --- + +// SLIDE + +// --- + + +// ## never + +type OnlyElementOf = T extends Array ? E : never; + +type SomeNumber2 = OnlyElementOf; +// type SomeNumber2 = never + +declare function elementAt(array: T, index: number): OnlyElementOf; + +let _elementAtArray = elementAt(_mixedArray2, 0); +// let _elementAtArray: string | number[] | null + +let _elementAtObject = elementAt({ a: 1 }, 0); +// let _elementAtObject: never + + +// --- + +// SLIDE + +// --- + + +// ## Filtering with never + +type PrimitiveOnlyElementOf = T extends Array + ? (E extends object ? never : E) + : never; + +type MixedArray2PrimitiveElement = PrimitiveOnlyElementOf; +// type MixedArray2PrimitiveElement = string | null + + +// --- + +// SLIDE + +// --- + + +// ## A useful example + +type GetReturnType = T extends (...args: any) => infer R ? R : never; + +type DateNowReturnType = GetReturnType; +// type DateNowReturnType = number + +let _mixedArray2ElementAt = (index: number) => elementAt(_mixedArray2, index); +type MixedArray2ElementAtResult = GetReturnType; +// type MixedArray2ElementAtResult = string | number[] | null + + +// --- + +// SLIDE + +// --- + + +// ## This Already exists + +type DateNowReturnType2 = ReturnType +// type DateNowReturnType2 = number + +// see 006 for exact definition + + + +// --- + +// NEXT + +// --- + diff --git a/006.ts b/006.ts new file mode 100644 index 0000000..6bb286b --- /dev/null +++ b/006.ts @@ -0,0 +1,209 @@ +// # Utility Types + +// https://www.typescriptlang.org/docs/handbook/utility-types.html + + +// --- + +// SLIDE + +// --- + + +// ## What we know + + +// --- + +// SLIDE + +// --- + + +// ### ReturnType + +type _ReturnType any> = T extends (...args: any) => infer R ? R : any; + + +// --- + +// SLIDE + +// --- + + +// ### Record + +type _Record = { + [P in K]: T; +}; + +type ThisIsARecord = Record; + +type ThisIsARecordResult = { + [x: string]: number[]; +}; + + +// --- + +// SLIDE + +// --- + + +// ### Partial + +type _Partial = { + [P in keyof T]?: T[P]; +}; + +type ThisIsAPartialObject = Partial<{ a: number; b: string; }>; + +type ThisIsAPartialObjectResult = { + a?: number; + b?: string; +}; + + +// --- + +// SLIDE + +// --- + + +// ### Readonly + +type _Readonly = { + readonly [P in keyof T]: T[P]; +}; + +type ThisIsAReadonlyObject = Readonly<{ a: number; b: string; }>; + +type ThisIsAReadonlyObjectResult ={ + readonly a: number; + readonly b: string; +}; + + +// --- + +// SLIDE + +// --- + + +// ## Several others + +// --- + +// SLIDE + +// --- + + +// ### Required + +type _Required = { + [P in keyof T]-?: T[P]; +}; + +type ThisIsTheOppositeOfPartial = Required + +type ThisIsTheOppositeOfPartialResult = { + a: number; + b: string; +}; + + +// --- + +// SLIDE + +// --- + + +// ### Parameters + +type _Parameters any> = + T extends (...args: infer P) => any ? P : never; + +type ArrayMapParameters = Parameters; +// [callbackfn: (value: any, index: number, array: any[]) => unknown, thisArg?: any] + + +// --- + +// SLIDE + +// --- + + +// ### ConstructorParameters + +type _ConstructorParameters any> = + T extends abstract new (...args: infer P) => any ? P : never; + +type DateConstructorParameters = ConstructorParameters; +// [value: string | number | Date] + + +// --- + +// SLIDE + +// --- + + +// ### Pick + +type _Pick = { + [P in K]: T[P]; +}; + +type ObjectWithA = Pick<{ a: number; b: string; }, 'a'>; +// { a: number; } + + +// --- + +// SLIDE + +// --- + + +// ### Omit + +type _Exclude = T extends U ? never : T; + +type _Omit = Pick>; + +type ObjectWithoutA = Omit<{ a: number; b: string; }, 'a'>; +// { b: string; } + + +// --- + +// SLIDE + +// --- + + +// ### NonNullable + +type _NonNullable = T extends null | undefined ? never : T; + +// useless if "strictNullChecks": false + +type ThisObjectAlwaysExists = NonNullable<{ a: number } | null>; +// { a: number; } + + + +// --- + +// NEXT + +// --- + diff --git a/007.ts b/007.ts new file mode 100644 index 0000000..d9c8f4e --- /dev/null +++ b/007.ts @@ -0,0 +1,129 @@ +// # Custom Utility types + + +// --- + +// SLIDE + +// --- + + +// ### + +type Nullable = T | null; + + +// --- + +// SLIDE + +// --- + + +// ### + +/** + * `T | U` with all non matching properties set included as partial + */ +type InclusiveUnion = (T | U) & Partial & Partial; + + +// --- + +// SLIDE + +// --- + + +// ### + +/** + * Unwrap `Promise` + */ +type PromiseResult = T extends PromiseLike ? R : T; + +/** + * Unwrap `Promise` of an async function's `ReturnType`. + */ +type AwaitReturnType any> = PromiseResult>; + + +// --- + +// SLIDE + +// --- + + +// ### + +/** + * Recursive `Readonly` wrapper. + */ +type Immutable = T extends ImmutablePrimitive + ? T + : T extends Array + ? ImmutableArray + : T extends Map + ? ImmutableMap + : T extends Set + ? ImmutableSet + : ImmutableObject; + +type ImmutablePrimitive = undefined | null | boolean | string | number | symbol | bigint; +type ImmutableArray = ReadonlyArray>; +type ImmutableMap = ReadonlyMap, Immutable>; +type ImmutableSet = ReadonlySet>; +type ImmutableObject = { readonly [K in keyof T]: Immutable }; + + +// --- + +// SLIDE + +// --- + + +// ### + +/** + * Get all keys of `T` that are type `Type`. + * + * Accepts optional, nullable and intersection types, but **not union** types. + * @example + * type ExampleType = { + * a: 'x'; + * b: 'x' | null; + * c: 'x' | undefined; + * d?: 'x'; + * e: 'x' & 'y'; + * f: 'x' | 'y'; + * }; + * type ResultType = KeyOfType; // "a" | "b" | "c" | "d" | "e" + */ +declare type KeyOfType = { [K in keyof T]: T[K] extends Type ? K : never}[keyof T]; + + /** + * Get all keys of `T` that are assignable to `Type`. + * + * Accepts optional, nullable and union types, but **not intersection** types. + * @example + * type ExampleType = { + * a: 'x'; + * b: 'x' | null; + * c: 'x' | undefined; + * d?: 'x'; + * e: 'x' & 'y'; + * f: 'x' | 'y'; + * }; + * type ResultType = KeyWithType; // "a" | "b" | "c" | "d" | "f" + */ +declare type KeyWithType = { [K in keyof T]: Type extends T[K] ? K : never}[keyof T]; + + +// --- + +// NEXT + +// --- + diff --git a/008.tsx b/008.tsx new file mode 100644 index 0000000..2209f72 --- /dev/null +++ b/008.tsx @@ -0,0 +1,107 @@ +// # A few extra Tips + + +// --- + +// SLIDE + +// --- + + +// ## Type the JSON object + +// eslint-disable-next-line +interface _Stringified { } +type Stringified = string & _Stringified; + +declare interface JSON { + parse(text: Stringified, reviver?: (key: string, value: any) => any): T; + stringify(value: T, replacer?: (key: string, value: any) => any, space?: string | number): Stringified; + stringify(value: T, replacer?: (number | string)[] | null, space?: string | number): Stringified; +} + + +// --- + +// SLIDE + +// --- + + +// ## Export your constants `as const` + +// ...so you can mouseover their value + +export const APP_MIN_WIDTH = 420 as const; + +// and will get a deep readonly: + +export const GUEST = { + ACCOUNT: { id: -1 }, + PERMISSIONS: ['read'], + PERMISSIONS_AS_STRING: ['read'] as ReadonlyArray, +} as const; + + +// --- + +// SLIDE + +// --- + + +// ## How to write connected props + +// store.ts +interface RootStoreState { + targetReducer: { + lookup: { + [id: string]: { id: number; name: string; }; + }; + }; +} + +// ComponentToConnect/index.tsx +import * as React from 'react'; +import { connect, ConnectedProps } from 'react-redux'; + +interface OwnProps { + id: number; +} + +function mapStateToProps(rootState: RootStoreState, ownProps: OwnProps) { + return { + value: rootState.targetReducer.lookup[ownProps.id] + } +} + +const connector = connect(mapStateToProps); + +type Props = OwnProps & ConnectedProps; + +class ComponentToConnect extends React.Component {} + + +// --- + +// SLIDE + +// --- + + +// ## Final words + +// 1. Mouseover everything + +// 2. Don't be afraid to enter the d.ts files + +// 3. Take your time + + + +// --- + +// FIN + +// --- + diff --git a/package.json b/package.json index ada926f..c5bacd3 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "homepage": "https://github.com/pero5ar/typescript-examples#readme", "dependencies": { "react": "^17.0.2", + "react-redux": "^7.2.5", "typescript": "^4.4.2" }, "devDependencies": {