-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathts_intro.ts
711 lines (609 loc) · 19.2 KB
/
ts_intro.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
/********************************************/
// BASIC TYPES
function thingFunc (): never {
throw 'blah'
}
// boolean
const boolThing: boolean = true;
// number (integers, floats, negative, hex, binary, octat)
const numThing: number = 100;
// string
const strThing: string = 'hello';
// arrays
const boolArr: boolean[] = [true, false];
const numArr: Array<number> = [1, 2];
const nestedStringArr: string[][] = [['one', 'two'], ['three', 'four']];
const nestedNumberArr: Array<Array<number>> = [[10, 20], [30, 40]];
// tuples
const fancyArr: [number, string, boolean] = [5, 'hello', true];
// enum (rarely used directly)
enum Color { Red = 100, Green = 200, Blue = 255 };
Color.Red // 100
Color.Green // 200
Color.Blue // 255
Color[100] // 'Red'
Color[200] // 'Green'
Color[255] // 'Blue'
// null and undefined
const nullThing: null = null;
const undefinedThing: undefined = undefined;
// void (mostly for function return types)
let voidThing: void = undefined;
// voidThing = null // only without strictNullChecks;
// never - see FUNCTIONS and TYPE NARROWING
let neverThing: never;
// object (not often useful by itself) - see INTERFACES
let objectThing: object = {};
objectThing = [];
objectThing = String;
objectThing = /hello/g;
objectThing = () => {};
// objectThing = null // only without strictNullChecks;
// symbol
const symbolThing: symbol = Symbol();
// any (always avoid if possible)
let anyThing: any = true;
anyThing = 5;
anyThing = 'hello';
anyThing = [];
anyThing = {};
anyThing = () => {};
anyThing.indexOf('hello');
// unknown (type-safe version of any, new to TypeScript 3+) - see TYPE NARROWING
let unknownThing: unknown = true;
unknownThing = 5;
unknownThing = 'hello';
unknownThing = [];
unknownThing = {};
unknownThing = () => {};
// unknownThing.indexOf('hello'); // Error
/********************************************/
/********************************************/
// FUNCTIONS
// functions can type their parameters and return
function someFunction (someParam: string): number {
return Number(someParam);
}
// functions can have optional parameters
function haveSomeParams (needThis: string, notSoMuch?: number) {
return needThis + notSoMuch;
}
// variables can have function types prior to assignment
const functionCopy: (paramAlias: string) => number = someFunction;
// function types can be extracted using "typeof"
const similarFunction: typeof someFunction = (differentParam: string) => {
return Number(differentParam) + 1000;
}
// functions with a void return type can have no return
function voidFunction (): void {
// no return
}
// A 'never' return type indicates the end of the function is never reached
function neverFunction (): never {
throw new Error('broke');
}
// Calling a function incorrectly is a TS Error
function doSomething(someNumber: number) {}
// doSomething('hello'); // Error
/********************************************/
/********************************************/
// UNION TYPES
// variables can have multiple possible types
let stringOrNumber: string | number = 'five';
stringOrNumber = 5;
// typescript is pretty smart
let someArr: boolean[] | string[][] = [];
let first: boolean | string[] = someArr[0];
/********************************************/
/********************************************/
// LITERAL TYPES
// use literal types to be even stricter about the variables' possible values
const literalBool: false = false;
const literalNum: 2 | 4 | 6 = 2;
const literalString: 'red' | 'blue' | 'green' = 'red';
/********************************************/
/********************************************/
// TYPE INFERENCE
// typescript infers types when none are given
const inferFunction = (param: number) => true;
const inferObject = {
propA: 'hi',
propB: 2
};
const inferArray = ['hello', 5, true];
// typescript infers function return types
function inferBooleanReturn () {
return true;
}
// 'const' infers stricter types than 'let'
const inferHello = 'hello';
let inferString = 'hello';
const infer5 = 5;
let inferNumber = 5;
const inferTrue = true;
let inferBoolean = true;
/********************************************/
/********************************************/
// TYPE ASSERTIONS
// specific types can be asserted when the inferred type is too general
const assertLiteralProperty = {
propA: <'hi'>'hi',
propB: 2 as 2
};
const assertTuple = ['hello', 5, true] as ['hello', 5, true];
// use type assertion to narrow broad types (union types, 'unknown', or 'any')
function assertExample (someParam: unknown) {
const someParamAlias = someParam as string;
const someParamAlias2 = <number>someParam;
return someParam as boolean;
}
/********************************************/
/********************************************/
// TYPE NARROWING
// typescript narrows types based on "type guards"
function typeNarrowExample (start: string | number | (string | number)[]) {
// instanceof is a built-in type guard
if (start instanceof Array) {
start.forEach((item) => {
// typeof is a built-in type guard
if (typeof item === 'string') {
item.search(/hello/);
} else {
item.toFixed(2);
}
});
} else if (typeof start === 'string') {
start.search(/hello/);
} else if (typeof start === 'number') {
start.toFixed(2);
} else {
// if all types have been narrowed, the type is 'never'
throw new Error(start);
}
}
// typescript will also narrow based on checking value or existence
function helloExample (testWord?: string) {
if (!testWord) return; // narrows to string
if(testWord === 'hello') {
console.log(testWord);
} else {
console.log('no hello');
}
}
// typescript allows for custom type guards
function isHello(word: string): word is 'hello' {
if (word === 'hello') {
return true;
} else {
return false;
}
}
function helloExample2 (testWord: string) {
if (isHello(testWord)) {
console.log(testWord);
} else {
console.log('no hello');
}
}
/********************************************/
/********************************************/
// INTERFACES
// interfaces can be written inline
let someObject: {
first: string;
second: string[];
}
someObject = {
first: 'a',
second: ['b', 'c']
};
// interfaces can have optional properties
interface IBasic {
alwaysHere: string;
sometimesHere?: number;
}
const basic1: IBasic = {
alwaysHere: 'abc',
sometimesHere: 2
}
const basic2: IBasic = {
alwaysHere: 'def'
}
// interfaces can have readonly properties
interface IReadOnly {
readonly somethingA: string;
readonly somethingB: number;
}
const readOnlyExample: IReadOnly = {
somethingA: 'hello',
somethingB: 5
}
// readOnlyExample.somethingA = 'goodbye'; // Error
// interfaces can be used within other interfaces
interface INestedContent {
propC: boolean;
}
interface IExample {
propA: string;
propB: number;
nestedStuff: INestedContent;
}
const example: IExample = {
propA: 'hello',
propB: 5,
nestedStuff: {
propC: true
}
};
// interfaces can be recursive
interface IRecurse {
level: number;
content?: IRecurse;
}
const recursion: IRecurse = {
level: 0,
content: {
level: 1,
content: {
level: 2
}
}
}
// interfaces can have key signatures for string keys
interface IKeySigSimple {
[key: string]: boolean;
}
const keySig1: IKeySigSimple = {
something: true,
somethingElse: false,
otherThing: true
};
// interfaces can have key signatures for numeric keys
interface IKeySigNum {
[key: number]: boolean;
}
const keySig2: IKeySigNum = [true];
const keySig3: IKeySigNum = {
0: true,
100: false
};
// interfaces can have key signatures for both string and numeric keys
interface IKeySigBoth {
[key: string]: string | number;
[key: number]: number;
}
const keySig4: IKeySigBoth = {
50: 50,
fifty: 'fifty'
};
// interfaces can combine key signatures with property types
interface IKeySigComplex {
needThis: string;
child?: IKeySigComplex;
[key: string]: string | IKeySigComplex | undefined;
}
const keySigExample: IKeySigComplex = {
needThis: 'hooray',
extraStuff: 'hooray?',
reallyExtraStuff: 'hooray??',
child: {
needThis: 'blah',
totallyRandomStuffHere: 'yep'
}
};
// interfaces can have call signatures
interface IFunctionExample {
(propA: string, propB: number): boolean;
}
const myFunctionExample: IFunctionExample = (propAliasA: string, propAliasB: number) => true;
// interfaces can be type narrowed
function objectNarrowing (someParam: IKeySigComplex) {
someParam.needThis.search(/hello/); // we know 'needThis' exists
if (someParam.child) {
someParam.child.needThis = 'goodbye'; // now we know 'child.needThis' exists
}
}
// interfaces can extend other interfaces
interface IParentA {
parentAProp: string;
}
interface IParentB {
parentBProp: string[];
}
interface IChild extends IParentA, IParentB {
childProp: number;
}
const child: IChild = {
parentAProp: 'hi',
parentBProp: ['hello'],
childProp: 5
};
/********************************************/
/********************************************/
// CLASSES
// class properties are public by default, but can be private or protected (or readonly)
class ClassA {
public propA: string = 'initial value';
private propB: number = 0;
protected propC: boolean = true;
readonly propD: string[] = [];
}
const classA = new ClassA();
// classes can declare and optionally initialize properties in constructor params
class ClassB {
constructor(
public propA: string,
private propB: number,
protected propC: boolean = true,
readonly propD: string[] = []
) {}
}
const classB = new ClassB('abc', 5, false, ['def']);
// classes can implement interfaces
interface IClassStuff {
propA: string;
someMethod(someParam: number): boolean;
}
class ClassC implements IClassStuff {
propA: string = 'hello';
someMethod(paramAlias: number) {
return true;
}
}
const classC = new ClassC();
// abstract classes can be extended, but not instantiated
abstract class Parent {
abstract propA: string;
abstract someMethod (someParam: number): boolean;
}
class Child extends Parent {
propA: string = 'hello';
someMethod (paramAlias: number) {
return true;
}
}
const classChild = new Child();
// const classParent = new Parent(); // Error
// classes can have static properties
class StaticExample {
static propA: string = 'i am a static property';
propB: string = 'i am an instance property';
static methodA () {
return 'i am a static method';
}
methodB () {
return 'i am an instance method';
}
}
StaticExample.propA;
StaticExample.methodA();
const staticExample = new StaticExample();
staticExample.propB;
staticExample.methodB();
// interfaces can extend classes
class Something {}
interface ISomething extends Something {}
/********************************************/
/********************************************/
// INTERSECTION TYPES
// intersection types let you treat variables like multiple types at the same time
function intersectionExample1 (stringAndNumber: String & Number) {
stringAndNumber.search(/hello/); // String method
stringAndNumber.toFixed(2); // Number method
}
// intersecting interfaces will combine their properties
interface IExampleA {
propA: string;
}
interface IExampleB {
propB: number;
}
function intersectionExample2 (aAndB: IExampleA & IExampleB) {
aAndB.propA.search(/hello/);
aAndB.propB.toFixed(2);
}
// Compare to union type
function unionExample (aOrB: IExampleA | IExampleB) {
// Have to type narrow to reach propA or propB
if ('propA' in aOrB) {
aOrB.propA.search(/hello/);
} else {
aOrB.propB.toFixed(2);
}
}
/********************************************/
/********************************************/
// TYPE ALIASES
// types can be declared explicitly, for later reference
type TSomething = number | string | {propA: string};
const something: TSomething = 5;
// type aliases can often be used instead of interfaces -- also see MAPPED TYPES
type TObject = {
someKeyName: string;
[key: string]: boolean | string;
}
/********************************************/
/********************************************/
// GENERIC TYPES
// A type parameter can be passed to a type for more dynamic typing
type TGeneric1<T> = number | string | T;
let genericThing: TGeneric1<boolean> = 5;
genericThing = 'hello';
genericThing = true;
// You can have multiple type parameters of any name
type TGeneric2<U, V> = {
propA: U;
propB: V;
}
const genericThing2: TGeneric2<string, number> = {
propA: 'hello',
propB: 5
};
// Functions can be generic
function genericFun<T>(prop: T, extra?: T[]) {
return prop;
}
genericFun(true, [false]); // The type parameter is implicitly the type of the first function parameter
genericFun<number>(5); // The type parameter can also be explicitly passed
// Interfaces can be generic
interface IGenObj<T> {
propA: T;
}
const genericObj: IGenObj<string> = {
propA: 'hi'
};
// Classes can be generic
class Action<T> {
constructor (
public state: T
) {}
run() {
return this.state;
}
}
const action = new Action({
someStateProp: true
});
const newState = action.run();
// Type parameters can be constrained using the "extends" keyword
type TConstrained<T extends string | number> = T;
let thing: TConstrained<string>;
function constrainedFun<T extends string[]>(param: T) {
return param.map((str) => str += '!');
}
constrainedFun(['hello']);
// Type parameters can be constrained by other type parameters
function addToArr<T, U extends T[]>(newItem: T, arr: U) {
arr.push(newItem);
return arr;
}
addToArr(5, [1, 2]);
/********************************************/
/********************************************/
// INDEX TYPES
// keyof, aka the "index type query operator", returns a union type of an interface's property name strings
type TIndexable = {
propA: string;
propB: number;
propC: boolean;
}
type TKeys = keyof TIndexable; // "propA" | "propB" | "propC"
// keyof will also give you numerical indices and method names
type TArrayKeys = keyof ['hello']; // number | "0" | "length" | "pop" | "push" | ...etc...
// T[K], aka the "indexed access operator" can give you the type of a given property
type TPropType = TIndexable['propA']; // string
// When combined, you can get a union of ALL of an object's property types
type TPropTypesAll = TIndexable[keyof TIndexable]; // string | number | boolean
/********************************************/
/********************************************/
// DISCRIMINATED UNIONS
// A union of interfaces can be used for an object with multiple possible structures
interface IPossibilityA {
label: 'a';
propA: string;
extraA: string;
}
interface IPossibilityB {
label: 'b';
propB: boolean;
extraB: boolean;
}
interface IPossibilityC {
label: 'c';
propC: number;
extraC: number;
}
type TCombo = IPossibilityA | IPossibilityB | IPossibilityC;
// The "discriminant" of two or more interfaces is the set of common properties
type TComboKeys = keyof TCombo; // 'label'
// ONLY the discriminant properties are guaranteed to exist
function comboTest(param: TCombo) {
param.label; // this is fine
// param.propB; // but this would error
// param.extraB; // also an error
}
// After type narrowing to one interface, TypeScript will assume all the interface's props exist
function comboNarrow(param: TCombo) {
if ('propB' in param) {
param.propB // Fine now
param.extraB // Also fine! Even though we didn't check for it, it's inferred with propB
}
}
/********************************************/
/********************************************/
// MAPPED TYPES
// Type aliases can define types for specific property names
type TKeyNames = 'propA' | 'propB' | 'propC';
type TKeyMap = {
[key in TKeyNames]: boolean;
}
// Using keyof can help create types that are modifications of another type
type TUser = {
username: string;
password: string;
}
type TPartialUser = {
[key in keyof TUser]?: TUser[key];
}
type TReadOnlyUser = {
readonly [key in keyof TUser]: TUser[key];
}
// Using in conjunction with generic types make these even more powerful
type TPartial<T> = {
[key in keyof T]?: T[key];
}
type TPartialUserGen = TPartial<TUser>;
// Some mapped types are useful enough that TypeScript made them built-in types - see PREDEFINED / BUILT-IN TYPES
type TPartialUserBuiltIn = Partial<TUser>;
type TReadOnlyUserBuildIn = Readonly<TUser>;
type TUsername = Pick<TUser, 'username'>;
type TUserWithRecord = Record<'username' | 'password', string>
/********************************************/
/********************************************/
// CONDITIONAL TYPES
// This conditional type says: If 0 is assignable to number (which it is), TConditional1 is 'string', otherwise it's 'boolean'.
type TConditional1 = 0 extends number ? string : boolean;
// This conditional type says: If 0 is assignable to string (which it is NOT), TCondtional2 is 'string', otherwise it's 'boolean'.
type TConditional2 = 0 extends string ? string : boolean;
// More helpful than the above (which really don't need to be conditional) is combining Conditional and Generic types
type TConditional3<T> = T extends number ? string[] : boolean[];
type TStringArr = TConditional3<5>; // string[]
type TBoolArr = TConditional3<number[]> // boolean[]
// Conditional Types can be used to filter union types (each type in the union is checked, and 'never' will remove the type from the union)
type TFilter<T> = T extends number | string ? T : never;
type TFilterTest1 = TFilter<5 | 'a' | boolean | any[]>;
// Conditional Types can be used with Mapped Types to do crazy things
type TSimpleObj = {
propA: string;
propB: number;
propC: boolean;
}
type TCrazyMap<T> = {
[key in keyof T]:
key extends 'propA'
? T[key][]
: T[key] extends boolean
? boolean[]
: key | T[key] | null;
}
type TSimplyCrazy = TCrazyMap<TSimpleObj>;
// Use "infer" to define a type variable dynamically based on the type being tested
type TInfer<T> = T extends infer R ? R : never;
type TInferred = TInfer<string>;
// The structure and location of the "infer" statement will determine what type is inferred
type TInferReturnType<T> = T extends () => infer R ? R : never;
type TInferredReturn = TInferReturnType<() => string>;
// TypeScript includes several conditional types as built-in types - see PREDEFINED / BUILT-IN TYPES
type TExcludeExample = Exclude<'a' | 4 | true, string>;
type TExtractExample = Extract<'a' | 4 | true, string>;
type TNonNullableExample = NonNullable<string | null | undefined>;
type TReturnTypeExample = ReturnType<() => (() => string)>;
class SomeClass {}
type TClassType = typeof SomeClass; // Use typeof to get the Static Class type
type TInstanceTypeExample = InstanceType<TClassType>; // Use InstanceType to get back to the instance type
/********************************************/
/**FUTURE SECTIONS***************************/
// PREDEFINED / BUILT-IN TYPES
// PROJECT CONFIGURATION
// TSLINT
/********************************************/