-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.ts
199 lines (176 loc) · 5.68 KB
/
index.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
(
/**
* @description when using type definitions with no explicity
* for example unions, we need to provide 'type guards' in order
* to ensure type safety. Typescript analyzes those phrases and
* casts the object to the type guard definition under the hood.
*
* type guard: (typeof {object} === {type})
*
* @param id number | string
* @returns boolean
*/
function typeGuard(id: number | string): boolean {
let result = false;
try {
// @ts-expect-error
result = (id.includes("admin"));
} catch (error) {
console.log("Property 'includes' does not exist on type 'number'.")
}
// OK
result = (typeof id === 'string' && id.includes("admin"))
return result;
}
)("admin");
(
/**
* @description we can also use equality to narrow the type resolution,
* giving us a crossed type. The only case when 'a' and 'b' are equal
* (value and type) is when they are strings, allowing the compiler to be
* sure of the interfaces on the object within the block scope of that
* equlity narrowing.
*
*/
function equality(a: string | number, b: string | boolean): void {
try {
// @ts-expect-error
a.toLowerCase();
} catch {
console.log("Property 'a' can be a number, which does not have an interface for 'toLowerCase'.");
}
// OK,
if (a === b) {
a.toLowerCase();
}
}
)(5, false);
(
/**
* @description the "in" operator is used to check if a specified
* property exists in an object or if an element exists in an array.
* For TS compiler, it provides ensurance of the type by narrowing
* down the types that fit the described interface.
*
*/
function contained() {
type fish = { swim: () => void, eat: () => void };
type bird = { fly: () => void, eat: () => void };
function move(animal: fish | bird) {
try {
// @ts-expect-error
animal.swim();
} catch {
console.log("Argument 'animal' might by of type bird, which cannot 'swim'.");
}
try {
if ("eat" in animal) {
// @ts-expect-error
animal.swim();
}
} catch {
console.log("Argument 'animal' might have an interface 'eat', but theres not enough narrowing of the exact type.")
}
// OK
animal.eat();
if ("swim" in animal) {
// OK
animal.swim();
}
}
}
)();
(
/**
* @description you can narrow down the type of an object in a
* conditional block, allowing TypeScript to infer more specific
* types within that block. This enables you to perform
* type-specific operations or access properties and methods
* that are only available on the narrowed type.
*/
function instance(x: Date | string) {
try {
// @ts-expect-error
console.log(x.toUTCString());
} catch {
console.log("Property 'toUTCString' does not exist on type 'string'");
}
if (x instanceof Date) {
// OK
console.log(x.toUTCString());
} else {
// OK
console.log(x.toUpperCase());
}
}
)(new Date());
(
/**
* @description a mechanism that allows you to narrow down
* the type of a variable based on the result of an assignment
* or a comparison operation. TypeScript uses control flow
* analysis to determine the narrowed type of a variable
* within specific code blocks.
*/
function assignment() {
let x = Math.random() < 0.5 ? 10 : "message";
try {
// @ts-expect-error
x = true;
} catch {
console.log("Type 'boolean' is not assignable to type 'string | number'.");
}
// OK
x = 5;
// OK
x = "another_message";
}
)();
(
/**
* @description the return type of our function expects a string or a
* number. Given our 'x' initialized with a boolean value, would throw
* an error unless the code changes the type of 'x' to either string or
* number. The moment we have a condition on the else statement, theres
* a execution possibility in which neither conditions are met, and 'x'
* type stays boolean, which is unaccepted return type, aka controlling
* the flow.
*/
function controlFlow(): string | number {
let x: string | number | boolean;
// Error
x = true;
if (Math.random()) {
x = "message";
// OK
console.log(x.toLowerCase());
} else {
// OK
x = 5;
}
return x;
}
)();
(
/**
* @description narrow down the type of a variable within a conditional
* block using a user-defined type predicate. It involves creating a
* custom type guard function that returns a boolean value indicating
* whether a variable satisfies a specific condition.
*/
function predicate(input: string | number) {
function isString(value: any): value is string {
return typeof value === "string";
}
try {
// @ts-expect-error
input.toUpperCase();
} catch {
console.log("Property 'toUpperCase' does not exist on type 'number'.");
}
if (isString(input)) {
// OK
console.log(input.toUpperCase());
}
}
)("message");