Security Theater
SecTheater is an online teaching community that targets the IT department. We do our best to produce for you high quality and well-edited screen casts about web development.
TypeScript is a strong static typed compiled superset of JavaScript
Yeah sure..
TypeScript builds on top of JavaScript. First, you write the TypeScript code. Then, you compile the TypeScript code into plain JavaScript code using a TypeScript compiler.
TypeScript is a strong static typed compiled superset of JavaScript
function add(x: number, y: number): number {
return x + y
}
TypeScript is a strong static typed compiled superset of JavaScript
function add(x: number, y: number): number {
return x + y
}
add('2', '3')
TypeScript is a strong static typed compiled superset of JavaScript
TypeScript is a strong static typed compiled superset of JavaScript
function add(x: number, y: number): number {
return x + y
}
add(2, 3)
index.ts
function add(x, y) {
return x + y
}
add(2, 3)
index.js
ts-compiler
More intentful code
More optimizable code
IDE support && type inference
Required skill nowadays
You'll write less code
More intentful code
More optimizable code
IDE support && type inference
Required skill nowadays
You'll write less code
function add(x, y) {
return x + y
}
function grossSalary(salary, bonus) {
return salary + salary * bonus
}
console.log(add(2, '3')) // 23
console.log(grossSalary('2000', 0.3)) // 2000600 WTF!
More intentful code
More optimizable code
IDE support && type inference
Required skill nowadays
You'll write less code
function add(x: number, y: number) {
return x + y
}
function grossSalary(salary: number, bonus: number) {
return salary + salary * bonus
}
console.log(add(2, '3')) // TypeError
console.log(grossSalary('2000', 0.3)) // TypeError
More intentful code
More optimizable code
IDE support && type inference
Required skill nowadays
You'll write less code
More intentful code
More optimizable code
IDE support && type inference
Required skill nowadays
You'll write less code
function add(x: number, y: number): number {
return x + y
}
for (let i of Array(1e4).keys()) {
console.log(add(i, i + 1))
}
"use strict";
function add(x, y) {
return x + y;
}
for (let i of Array(1e4).keys()) {
console.log(add(i, i + 1));
}
More intentful code
More optimizable code
IDE support && type inference
Required skill nowadays
You'll write less code
"use strict";
function add(x, y) {
return x + y;
}
for (let i of Array(1e4).keys()) {
console.log(add(i, i + 1));
}
node --print-opt-code index.js > opt-code.txt
More intentful code
More optimizable code
IDE support && type inference
Required skill nowadays
You'll write less code
--- Optimized code ---
optimization_id = 19
source_position = 10348
kind = OPTIMIZED_FUNCTION
stack_slots = 8
compiler = turbofan
address = 0x247a506095c1
Instructions (size = 716)
0x247a50609620 0 488b59c0 REX.W movq rbx,[rcx-0x40]
0x247a50609624 4 f6430f01 testb [rbx+0xf],0x1
0x247a50609628 8 740d jz 0x247a50609637 <+0x17>
0x247a5060962a a 49ba80d93d0100000000 REX.W movq r10,0x13dd980 (CompileLazyDeoptimizedCode)
0x247a50609634 14 41ffe2 jmp r10
0x247a50609637 17 55 push rbp
0x247a50609638 18 4889e5 REX.W movq rbp,rsp
0x247a5060963b 1b 56 push rsi
0x247a5060963c 1c 57 push rdi
0x247a5060963d 1d 4883ec20 REX.W subq rsp,0x20
0x247a50609641 21 488975e0 REX.W movq [rbp-0x20],rsi
0x247a50609645 25 48897de8 REX.W movq [rbp-0x18],rdi
0x247a50609649 29 493b65e0 REX.W cmpq rsp,[r13-0x20] (external value (StackGuard::address_of_jslimit()))
0x247a5060964d 2d 0f86a1010000 jna 0x247a506097f4 <+0x1d4>
0x247a50609653 33 488b75e0 REX.W movq rsi,[rbp-0x20]
0x247a50609657 37 488b7e1f REX.W movq rdi,[rsi+0x1f]
0x247a5060965b 3b 4c8b4617 REX.W movq r8,[rsi+0x17]
0x247a5060965f 3f 4d8b802f020000 REX.W movq r8,[r8+0x22f]
0x247a50609666 46 4d394528 REX.W cmpq [r13+0x28] (root (the_hole_value)),r8
0x247a5060966a 4a 0f84e1010000 jz 0x247a50609851 <+0x231>
0x247a50609670 50 40f6c701 testb rdi,0x1
0x247a50609674 54 0f8412020000 jz 0x247a5060988c <+0x26c>
0x247a5060967a 5a 4c8b4fff REX.W movq r9,[rdi-0x1]
0x247a5060967e 5e 4d394d78 REX.W cmpq [r13+0x78] (root (symbol_map)),r9
0x247a50609682 62 0f8510020000 jnz 0x247a50609898 <+0x278>
0x247a50609688 68 493bf8 REX.W cmpq rdi,r8
0x247a5060968b 6b 0f8410000000 jz 0x247a506096a1 <+0x81>
0x247a50609691 71 488b7e27 REX.W movq rdi,[rsi+0x27]
0x247a50609695 75 49c7c502000000 REX.W movq r13,0x2 ;; debug: deopt position, script offset '10629'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason 'Insufficient type feedback for generic named access'
;; debug: deopt index 2
0x247a5060969c 7c e8bf890b00 call 0x247a506c2060 ;; soft deoptimization bailout
0x247a506096a1 81 488b5627 REX.W movq rdx,[rsi+0x27]
0x247a506096a5 85 48b9e10b7a2aaa390000 REX.W movq rcx,0x39aa2a7a0be1 ;; object: 0x39aa2a7a0be1 <String[#7]: _stdout>
0x247a506096af 8f b802000000 movl rax,0x2
0x247a506096b4 94 488bfe REX.W movq rdi,rsi
0x247a506096b7 97 49ba0002410100000000 REX.W movq r10,0x1410200 (LoadICTrampoline)
0x247a506096c1 a1 41ffd2 call r10
0x247a506096c4 a4 488945d8 REX.W movq [rbp-0x28],rax
0x247a506096c8 a8 488b7d10 REX.W movq rdi,[rbp+0x10]
0x247a506096cc ac 49397d30 REX.W cmpq [r13+0x30] (root (null_value)),rdi
0x247a506096d0 b0 0f8413010000 jz 0x247a506097e9 <+0x1c9>
0x247a506096d6 b6 4c8b45d8 REX.W movq r8,[rbp-0x28]
0x247a506096da ba 41f6c001 testb r8,0x1
0x247a506096de be 0f84cc010000 jz 0x247a506098b0 <+0x290>
0x247a506096e4 c4 49b9d92440251e020000 REX.W movq r9,0x21e254024d9 ;; object: 0x021e254024d9 <Map(HOLEY_ELEMENTS)>
0x247a506096ee ce 4d3948ff REX.W cmpq [r8-0x1],r9
0x247a506096f2 d2 0f85c4010000 jnz 0x247a506098bc <+0x29c>
0x247a506096f8 d8 4d8b4817 REX.W movq r9,[r8+0x17]
0x247a506096fc dc 4d8b89df000000 REX.W movq r9,[r9+0xdf]
0x247a50609703 e3 4d394d40 REX.W cmpq [r13+0x40] (root (false_value)),r9
0x247a50609707 e7 0f8438000000 jz 0x247a50609745 <+0x125>
0x247a5060970d ed 4d394d48 REX.W cmpq [r13+0x48] (root (empty_string)),r9
0x247a50609711 f1 0f842e000000 jz 0x247a50609745 <+0x125>
0x247a50609717 f7 4d8b59ff REX.W movq r11,[r9-0x1]
0x247a5060971b fb 41f6430d10 testb [r11+0xd],0x10
0x247a50609720 100 0f851f000000 jnz 0x247a50609745 <+0x125>
0x247a50609726 106 4d399dc0000000 REX.W cmpq [r13+0xc0] (root (heap_number_map)),r11
0x247a5060972d 10d 0f8409010000 jz 0x247a5060983c <+0x21c>
0x247a50609733 113 4d399d40010000 REX.W cmpq [r13+0x140] (root (bigint_map)),r11
0x247a5060973a 11a 0f84ed000000 jz 0x247a5060982d <+0x20d>
0x247a50609740 120 e9a4000000 jmp 0x247a506097e9 <+0x1c9>
0x247a50609745 125 498b501f REX.W movq rdx,[r8+0x1f]
0x247a50609749 129 49395520 REX.W cmpq [r13+0x20] (root (undefined_value)),rdx
0x247a5060974d 12d 0f847c000000 jz 0x247a506097cf <+0x1af>
0x247a50609753 133 48b9f9af0c521a1e0000 REX.W movq rcx,0x1e1a520caff9 ;; object: 0x1e1a520caff9 <String[#5]: error>
0x247a5060975d 13d b804000000 movl rax,0x4
0x247a50609762 142 48bb8197f66f783e0000 REX.W movq rbx,0x3e786ff69781 ;; object: 0x3e786ff69781 <FeedbackVector[6]>
0x247a5060976c 14c 48be4948cc952a280000 REX.W movq rsi,0x282a95cc4849 ;; object: 0x282a95cc4849 <FunctionContext[56]>
0x247a50609776 156 49ba8002410100000000 REX.W movq r10,0x1410280 (KeyedLoadIC)
0x247a50609780 160 41ffd2 call r10
0x247a50609783 163 a801 test al,0x1
0x247a50609785 165 0f8426000000 jz 0x247a506097b1 <+0x191>
0x247a5060978b 16b 488b78ff REX.W movq rdi,[rax-0x1]
0x247a5060978f 16f 0fb67f0d movzxbl rdi,[rdi+0xd]
0x247a50609793 173 83e712 andl rdi,0x12
0x247a50609796 176 83ff02 cmpl rdi,0x2
0x247a50609799 179 0f8512000000 jnz 0x247a506097b1 <+0x191>
0x247a5060979f 17f b801000000 movl rax,0x1
0x247a506097a4 184 488b7d10 REX.W movq rdi,[rbp+0x10]
0x247a506097a8 188 4c8b45d8 REX.W movq r8,[rbp-0x28]
0x247a506097ac 18c e923000000 jmp 0x247a506097d4 <+0x1b4>
0x247a506097b1 191 49394520 REX.W cmpq [r13+0x20] (root (undefined_value)),rax
0x247a506097b5 195 0f840c000000 jz 0x247a506097c7 <+0x1a7>
0x247a506097bb 19b 49c7c507000000 REX.W movq r13,0x7 ;; debug: deopt position, script offset '21107'
;; debug: deopt position, inlining id '0'
;; debug: deopt reason 'Insufficient type feedback for generic named access'
;; debug: deopt index 7
0x247a506097c2 1a2 e899880b00 call 0x247a506c2060 ;; soft deoptimization bailout
0x247a506097c7 1a7 488b7d10 REX.W movq rdi,[rbp+0x10]
0x247a506097cb 1ab 4c8b45d8 REX.W movq r8,[rbp-0x28]
0x247a506097cf 1af 33c0 xorl rax,rax
0x247a506097d1 1b1 4c8bc8 REX.W movq r9,rax
0x247a506097d4 1b4 83f800 cmpl rax,0x0
0x247a506097d7 1b7 0f850c000000 jnz 0x247a506097e9 <+0x1c9>
0x247a506097dd 1bd 49c7c508000000 REX.W movq r13,0x8 ;; debug: deopt position, script offset '11128'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason 'Insufficient type feedback for generic named access'
;; debug: deopt index 8
0x247a506097e4 1c4 e877880b00 call 0x247a506c2060 ;; soft deoptimization bailout
0x247a506097e9 1c9 498b4520 REX.W movq rax,[r13+0x20] (root (undefined_value))
0x247a506097ed 1cd 488be5 REX.W movq rsp,rbp
0x247a506097f0 1d0 5d pop rbp
0x247a506097f1 1d1 c21000 ret 0x10
0x247a506097f4 1d4 48bf0000000088000000 REX.W movq rdi,0x8800000000
0x247a506097fe 1de 57 push rdi
0x247a506097ff 1df b801000000 movl rax,0x1
0x247a50609804 1e4 48bba0cd090100000000 REX.W movq rbx,0x109cda0
0x247a5060980e 1ee 48be210160e9fc1a0000 REX.W movq rsi,0x1afce9600121 ;; object: 0x1afce9600121 <NativeContext[243]>
0x247a50609818 1f8 488bf8 REX.W movq rdi,rax
0x247a5060981b 1fb 49ba4063440100000000 REX.W movq r10,0x1446340 (CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit)
0x247a50609825 205 41ffd2 call r10
0x247a50609828 208 e926feffff jmp 0x247a50609653 <+0x33>
0x247a5060982d 20d 41f74107feffff7f testl [r9+0x7],0x7ffffffe
0x247a50609835 215 75b2 jnz 0x247a506097e9 <+0x1c9>
0x247a50609837 217 e909ffffff jmp 0x247a50609745 <+0x125>
0x247a5060983c 21c c4c17b104107 vmovsd xmm0,[r9+0x7]
0x247a50609842 222 c5f157c9 vxorpd xmm1,xmm1,xmm1
0x247a50609846 226 c5f92ec8 vucomisd xmm1,xmm0
0x247a5060984a 22a 759d jnz 0x247a506097e9 <+0x1c9>
0x247a5060984c 22c e9f4feffff jmp 0x247a50609745 <+0x125>
0x247a50609851 231 49bb31bb792aaa390000 REX.W movq r11,0x39aa2a79bb31 ;; object: 0x39aa2a79bb31 <String[#10]: kUseStdout>
0x247a5060985b 23b 4153 push r11
0x247a5060985d 23d 48897dd8 REX.W movq [rbp-0x28],rdi
0x247a50609861 241 4c8945d0 REX.W movq [rbp-0x30],r8
0x247a50609865 245 48bba0c3090100000000 REX.W movq rbx,0x109c3a0
0x247a5060986f 24f b801000000 movl rax,0x1
0x247a50609874 254 4c8bde REX.W movq r11,rsi
0x247a50609877 257 48be210160e9fc1a0000 REX.W movq rsi,0x1afce9600121 ;; object: 0x1afce9600121 <NativeContext[243]>
0x247a50609881 261 4c8b1595ffffff REX.W movq r10,[rip+0xffffff95]
0x247a50609888 268 41ffd2 call r10
0x247a5060988b 26b 90 nop
0x247a5060988c 26c 49c7c500000000 REX.W movq r13,0x0 ;; debug: deopt position, script offset '10578'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason 'Smi'
;; debug: deopt index 0
0x247a50609893 273 e8c8870300 call 0x247a50642060 ;; eager deoptimization bailout
0x247a50609898 278 49c7c501000000 REX.W movq r13,0x1 ;; debug: deopt position, script offset '10578'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason 'not a Symbol'
;; debug: deopt index 1
0x247a5060989f 27f e8bc870300 call 0x247a50642060 ;; eager deoptimization bailout
0x247a506098a4 284 49c7c503000000 REX.W movq r13,0x3 ;; debug: deopt position, script offset '10610'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason '(unknown)'
;; debug: deopt index 3
0x247a506098ab 28b e8b0870700 call 0x247a50682060 ;; lazy deoptimization bailout
0x247a506098b0 290 49c7c504000000 REX.W movq r13,0x4 ;; debug: deopt position, script offset '10670'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason 'Smi'
;; debug: deopt index 4
0x247a506098b7 297 e8a4870300 call 0x247a50642060 ;; eager deoptimization bailout
0x247a506098bc 29c 49c7c505000000 REX.W movq r13,0x5 ;; debug: deopt position, script offset '10670'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason 'wrong map'
;; debug: deopt index 5
0x247a506098c3 2a3 e898870300 call 0x247a50642060 ;; eager deoptimization bailout
0x247a506098c8 2a8 49c7c506000000 REX.W movq r13,0x6 ;; debug: deopt position, script offset '20971'
;; debug: deopt position, inlining id '0'
;; debug: deopt reason '(unknown)'
;; debug: deopt index 6
0x247a506098cf 2af e88c870700 call 0x247a50682060 ;; lazy deoptimization bailout
0x247a506098d4 2b4 49c7c509000000 REX.W movq r13,0x9 ;; debug: deopt position, script offset '10348'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason '(unknown)'
;; debug: deopt index 9
0x247a506098db 2bb e880870700 call 0x247a50682060 ;; lazy deoptimization bailout
0x247a506098e0 2c0 49c7c50a000000 REX.W movq r13,0xa ;; debug: deopt position, script offset '10582'
;; debug: deopt position, inlining id '-1'
;; debug: deopt reason '(unknown)'
;; debug: deopt index 10
0x247a506098e7 2c7 e874870700 call 0x247a50682060 ;; lazy deoptimization bailout
More optimizable code
IDE support && type inference
Required skill nowadays
You'll write less code
function add(x: number, y: number) {
return x + y
}
console.log(3, '2') // TypeError
function add(x, y) {
if (typeof x !== 'number') {
throw new Error(
`Type ${typeof x} is not assignable for number`,
)
}
if (typeof y !== 'number') {
throw new Error(
`Type ${typeof y} is not assignable for number`,
)
}
return x + y
}
console.log(add(3, '2')) // Error: Type string is not assignable for number
index.ts
index.js
IDE support && type inference
Required skill nowadays
You'll write less code
IDE support && type inference
Required skill nowadays
IDE support && type inference
Required skill nowadays
IDE support && type inference
Required skill nowadays
ζ sudo npm i -g typescript tsc ts-node
terminal
ζ tsc --init
terminal
{
"compilerOptions": {
"target": "es5",
"module": "commonjs"
}
}
tsconfig.json
console.log(something)
var something = 5
index.ts
"use strict";
console.log(something);
var something = 5;
//# sourceMappingURL=index.js.map
index.js
console.log(something)
let something = 5
index.ts
"use strict";
console.log(something);
let something = 5;
//# sourceMappingURL=index.js.map
index.js
let age: number = 42
let name = "ahmed"
Type Annotation
Type Inference
​number
string
boolean
null
undefined
void
​Object
Array
Function
​Any
Never
Unknown
Never
Any
Unknown
Any
let x: any = 5
x = 'ahmed'
x = [12, 3, 14]
x = {
y: 2,
}
Unknown
let x: unknown = 5
x = 'ahmed'
x = [12, 3, 14]
x = {
y: 2,
}
Unknown
let x: unknown
let y: number = 3
x = 'ahmed'
x = 15
y = x
All types are valid to be in place of uknown, but unknown can't be in place of a fixed type
Throws an error
Unknown
let x: unknown
let y: number = 3
x = 'ahmed'
x = 15
if (typeof x === 'number') {
y = x
}
All types are valid to be in place of uknown, but unknown can't be in place of a fixed type
Works again ^^
Object
let user: object
user = {
username: 'ahmedosama_st',
}
user = new Date()
class User {}
user = new User()
Object
let user: {
username: string,
github: string,
articles: []
}
user = {
username: "ahmedosama_st",
github: "https://ahmedosama-st.github.io/codefolio/",
articles: [
{
id: 1,
slug: "optimizing recursive functions",
url: "https://dev.to/ahmedosama_st/optimizing-recursive-functions-1lhh"
}
]
}
Object
let user: {
full_name: string
github: string
articles?: []
} = {
full_name: 'Ahmed Osama',
github: 'https://ahmedosama-st.github.io/codefolio/',
}
user.articles?.map((article) => console.log(article.title))
Object
let user: {
[k: string]: string
github: string
articles?: []
} = {
full_name: 'Ahmed Osama',
github: 'https://ahmedosama-st.github.io/codefolio/',
}
Object
enum Privilege {
User,
Moderator,
Admin,
}
console.log(Privilege.Admin)
enum OrderStatus {
Pending,
Delievered,
Accepted,
Rejected
}
Object
let { title, url }: { title: string; url: string } = {
title:
'Understanding hidden classes in V8 for more optimizations',
url: 'someblog.com/ahmedosama/v8-optimizations',
}
function sum({
first,
second,
}: {
first: number
second: number
}): number {
return first + second
}
Object
let reputations: number[] = [
184, 129, 312, 322, 51, 98, 672,
]
const fames: number[] = reputations.filter(
(reputation) => reputation > 125,
)
console.log(fames)
// [ 184, 129, 312, 322, 672 ]
Object
let skills: [string, number][] = [
['software design', 5],
['problem solving', 1],
['public speaking', 5],
['node.js', 5],
]
let results = skills.map(
([skill, score]) => `You scored ${score} in ${skill}`,
)
console.log(results)
/*
[
'You scored 5 in software design',
'You scored 1 in problem solving',
'You scored 5 in public speaking',
'You scored 5 in node.js'
]
*/
Object
let [title, url]: [string, string] = [
'Understanding hidden classes in V8 for more optimizations',
'someblog.com/ahmedosama/v8-optimizations',
]
function sum([first, second]: [
first: number,
second: number,
]): number {
return first + second
}
Chossing one of many types to be stored in a variable
let rules: string | string[] = 'required'
let rules: string | string[] = ['required', 'email', 'unique']
function flipCoin(): 'heads' | 'tails' {
if (Math.random() > 0.5) return 'heads'
return 'tails'
}
Creating new type of combining several types together in one type
let user: { username: string; password: string }
let instructor: { username: string; password: string } & {
courses: []
}
let admin: { username: string; password: string } & {
courses: []
} & { privilege: number }
type Rule = string | string[]
let rules: Rule = 'required'
let otherRules: Rule = ['required', 'email', 'unique']
type Rule = {
check(value: any, field?: string, data?: {}): boolean
__toString(): string
}
type Rules = string | string[] | Rule[]
let rules: Rules = 'required'
let otherRules: Rules = ['required', 'email', 'unique']
type User = { username: string; password: string }
type Instructor = User & {
courses: []
}
type Admin = User & Instructor & { privilege: number }
type Numbers = number | number[] | Numbers[]
let numbers: Numbers = [1, [2, 3, [3]]]
function sumRecursively(xs: Numbers) {
let res: number[] = []
if (typeof xs === 'number') {
res.push(xs)
} else {
xs.flat(Infinity).map((x) => res.push(x))
}
return res.reduce((x, y) => x + y, 0)
}
console.log(sumRecursively(numbers)) // 9
type linkedList = { value: any; next: linkedList | null }
let linkedList: linkedList = {
value: 2,
next: {
value: 23,
next: {
value: 15,
next: null,
},
},
}
The Never type is better used when you have a conditional branching and end up failing all your assertions
type Numbers = number | number[] | Numbers[]
function getSomeValue(): any {}
let x: Numbers = getSomeValue()
if (typeof x == 'number') {
x
} else if (Array.isArray(x)) {
x
} else {
x
}
number
number[] | Numbers[]
never
Exhaustive
Conditionals
The Never type is better used when you have a conditional branching and end up failing all your assertions
type Numbers = number | number[] | Numbers[] | string
function getSomeValue(): any {}
let x: Numbers = getSomeValue()
if (typeof x == 'number') {
x
} else if (Array.isArray(x)) {
x
} else {
x
}
number
number[] | Numbers[]
string
Exhaustive
Conditionals
class UnreachableCodeError extends Error {
constructor(_nvr: never, message: string) {
super(message)
}
}
type Numbers = number | number[] | Numbers[] | string
function getSomeValue(): any {}
let x: Numbers = getSomeValue()
if (typeof x == 'number') {
x
} else if (Array.isArray(x)) {
x
} else {
throw new UnreachableCodeError(
x,
`Expected x to be never, but x is ${typeof x}`,
)
}
TypeError for argument of type
string to be assigned to a
a parameter of type never
type Lecture = {}
type Instructor = {}
type Course = {
title: string
path_id: number | null
lectures: Lecture[]
instructor: Instructor
}
let course: unknown
if (
typeof course === 'object' &&
course !== null &&
'title' in course &&
typeof course['title'] === 'string' &&
'path_id' in course &&
(typeof course['path_id'] === 'number' ||
course['path_id'] === null) &&
'lectures' in course &&
Array.isArray(course['lectures']) &&
'instructor' in course
) {
course
} else {
course
}
object but we're not
sure that's it's the
course object
unknown type
type Lecture = {}
type Instructor = {}
type Course = {
title: string
path_id: number | null
lectures: Lecture[]
instructor: Instructor
}
function isCourse(course: any): course is Course {
return (
typeof course === 'object' &&
course !== null &&
'title' in course &&
typeof course['title'] === 'string' &&
'path_id' in course &&
(typeof course['path_id'] === 'number' ||
course['path_id'] === null) &&
'lectures' in course &&
Array.isArray(course['lectures']) &&
'instructor' in course
)
}
let x: unknown
if (isCourse(x)) {
x
} else {
x
}
object but this time we're
sure that it's the course object
and get more IDE support
unknown type
type Lecture = {}
type Instructor = {}
type Course = {
title: string
path_id: number | null
lectures: Lecture[]
instructor: Instructor
}
function isCourse(course: any): asserts course is Course {
if (
!(
typeof course === 'object' &&
course !== null &&
'title' in course &&
typeof course['title'] === 'string' &&
'path_id' in course &&
(typeof course['path_id'] === 'number' ||
course['path_id'] === null) &&
'lectures' in course &&
Array.isArray(course['lectures']) &&
'instructor' in course
)
)
throw new Error('Unexpected type')
}
let x: unknown
x
isCourse(x)
x
unknown type
Course
Returns void which means you can't return from this function
Null
Void
Undefined
Null
null in TypeScript can be though of as the 404 HTTP error, more like there's nothing existing in this variable
const user = {
first_name: 'Ahmed',
last_name: 'Osama',
email: 'ahmedosama@sectheater.io',
secondary_email: null,
}
The user never had a secondary email
Accessing a null value
const user = {
first_name: 'Ahmed',
last_name: 'Osama',
email: 'ahmedosama@sectheater.io',
secondary_email: null,
}
console.log(user.secondary_email.toUpperCase())
Note: "strict" option must be uncommented in tsconfig.json
Object may be null :/
Accessing a null value
const user = {
first_name: 'Ahmed',
last_name: 'Osama',
email: 'ahmedosama@sectheater.io',
secondary_email: null,
}
console.log(user.secondary_email!.toUpperCase())
Note: "strict" option must be uncommented in tsconfig.json
Telling typescript
to ignore if the object is null
I.E: shut up TS, I'm mature enough
undefined
undefined can be thought of as the delay of completing some task or a function. I.E. a placeholder for coming value in the future
const axios = require('axios')
let data: Post[]
;(async function getData() {
data = await axios.get(
'https://jsonplaceholder.typicode.com/users/1/posts',
)
})()
console.log(data)
type Post = {
userId: number
id: number
title: string
body: string
}
undefined
undefined
undefined can be thought of as the delay of completing some task or a function. I.E. a placeholder for coming value in the future
const task = {
created_at: new Date(),
todo: 'lorem ipsum',
assigned_to: 1,
completed_at: undefined,
}
The task is not yet done by
the user holding id 1
void
Void is better used with function return types
as an indication that the return value of this function is not usable or dependent on
function returnNothing(): void {
return null
}
void
Void is better used with function return types
as an indication that the return value of this function is not usable or dependent on
function returnNothing(): void {
return undefined
}
let btn = document.getElementById('clickable')
btn.addEventListener('click', () => console.log('hello'))
console.log(btn.click())
Maybe null?
If not null, what object it is?
Compilation error
Object may be null
let btn = document.getElementById('clickable')
btn.addEventListener('click', () => console.log('hello'))
console.log(btn.click())
<!DOCTYPE html>
<html lang="en">
<head>
<title>Get started</title>
</head>
<body>
<p id="clickable">I'm just a text, Don't click me :(</p>
<form>
<button type="submit" id="button">Click me</button>
</form>
<script src="dist/index.js"></script>
</body>
</html>
let btn = document.getElementById(
'clickable',
) as HTMLInputElement
btn.addEventListener('click', () => console.log('hello'))
console.log(btn.form)
Although it might not be the real button,
but you will get the IDE support as if it's a button
function add(x: number, y: number): number {
return x + y
}
const add: (x: number, y: number) => number = (x, y) =>
x + y
type MathematicalOperation = (
x: number,
y: number,
) => number
const add: MathematicalOperation = (x, y) => x + y
const subtract: MathematicalOperation = (x, y) => x - y
const multiply: MathematicalOperation = (x, y) => x * y
const divide: MathematicalOperation = (x, y) => x / y
function sum(numbers: number[]) {
return numbers.reduce((x, y) => x + y, 0)
}
console.log(sum([1, 2, 3, 3, 4])) // 13
function sum(numbers?: number[]) {
return numbers?.reduce((x, y) => x + y, 0)
}
console.log(sum()) // undefined
function sum(...numbers: number[]) {
return numbers.reduce((x, y) => x + y, 0)
}
console.log(sum()) // 0
console.log(sum(1, 2, 3, 3, 4)) // 13
function parseName([first_name, last_name]: [
string,
string,
]): string {
return `Hello ${first_name} ${last_name}`
}
console.log(parseName(['ahmed', 'osama'])) // Hello ahmed osama
type Point = {
x: number
y: number
z?: number
}
function parsePoint({ x, y }: Point): number {
return x + y
}
console.log(parsePoint({ x: 1, y: 2, z: 3 })) // 3
function add(x: number, y: number): number
function add(x: string, y: string): string
function add(x: any, y: any): any {
return x + y
}
console.log(add(2, 3)) // 5
console.log(add('ahmed', 'osama')) // ahmedosama
console.log(add('1', 2)) // TypeError
console.log(add(undefined, null)) // TypeError
function add(x: number, y: number): number
function add(x: string, y: string, title: string): string
function add(
x: number | string,
y: string | number,
title?: string,
): string | number {
if (typeof x === 'number' && typeof y === 'number') {
return x + y
} else {
return `${title}. ${x} ${y}`
}
}
console.log(add(2, 3)) // 5
console.log(add('ahmed', 'osama', 'Eng')) // Eng. ahmed osama
function len(s: string): number
function len(arr: any[]): number
function len(x: any): number {
return x.length
}
function len(x: string | any[]): number {
return x.length
}
type Article = {
id: number
slug: string
link: string
}
type User = {
username: string
articles: Article[]
}
function parseArticles(this: User) {
return this.articles.map(
(article) =>
`${this.username} wrote ${article.slug} you can read it from here ${article.link}`,
)
}
let user: User = {
username: 'ahmedosama',
articles: [
{
id: 1,
slug: 'work hard',
link: 'someurl.com/read',
},
],
}
console.log(...parseArticles.call(user))
type DescribableFunction = {
description: string
(someArg: number): boolean
}
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}
type DescribableAdder = {
description: string
(x: number, y: number): number
}
function doSomething(
fn: DescribableAdder,
x: number,
y: number,
) {
console.log(fn.description + ' returned ' + fn(x, y))
}
function add(x: number, y: number): number {
return x + y
}
add.description = 'Adds two numbers'
doSomething(add, 2, 3)
type SomeConstructor = {
new (x: number, y: number): Point
}
class Point {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
}
let generatePoint: SomeConstructor = Point
let p = new generatePoint(2, 3)
console.log(p)
The OOP you know from JS is still the same here in TS but you get more features from TS
class Point {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
calc(): number {
return this.x + this.y
}
}
Typing your properties
class Point {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
calc(): number {
return this.x + this.y
}
}
Property modifiers
Accessible from within the class, subclasses, and its instances
class Point {
protected x: number
protected y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
calc(): number {
return this.x + this.y
}
}
Property modifiers
Accessible from within the class, subclasses only
class Point {
private x: number
private y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
calc(): number {
return this.x + this.y
}
}
Property modifiers
Accessible from within the class only
class Point {
private x: number
private y: number
private readonly z: number = 5
constructor(x: number, y: number) {
this.x = x
this.y = y
}
calc(): number {
return this.x + this.y + this.z
}
}
Property modifiers
Equivalent to const keyword, creates an immutable property
class Point {
private static x: number
private static y: number
private readonly z: number = 5
constructor(x: number, y: number) {
Point.x = x
Point.y = y
}
calc(): number {
return Point.x + Point.y + this.z
}
}
Static members
class Point {
private readonly z: number = 5
constructor(private x: number, private y: number) {}
calc(): number {
return this.x + this.y + this.z
}
}
Parameter properties
> The mechanism of deriving a class from another class that share a set of attributes and methods (Sharing functionality)
Model
Post
Tweet
class HTMLElement {
public addEventListener(evt: HTMLEvent) {
// some logic
}
}
class HTMLInputElement extends HTMLElement {}
let evt = new HTMLEvent()
let input = new HTMLInputElement()
input.addEventListener(evt)
class HTMLButtonElement extends HTMLElement {}
let evt = new HTMLEvent()
let btn = new HTMLButtonElement()
btn.addEventListener(evt)
class HTMLElement {
protected el: string = 'abc'
public addEventListener(evt: HTMLEvent) {
// some logic
}
}
class HTMLInputElement extends HTMLElement {
public getElement(): string {
return this.el
}
}
let input = new HTMLInputElement()
console.log(input.getElement()) // "abc"
class HTMLButtonElement extends HTMLElement {
public getElement(): string {
return this.el
}
}
let btn = new HTMLButtonElement()
console.log(btn.getElement()) // "abc"
class Tweet {
private _id: string
constructor() {
this._id = 'd41d8cd98f00b204e9800998ecf8427e'
}
public get id() {
return this._id
}
public set id(newId: string) {
this._id = require('crypto')
.createHash('md5')
.update(newId)
.digest('hex')
}
}
let t = new Tweet()
console.log(t.id) // d41d8cd98f00b204e9800998ecf8427e
t.id = 'ahmed'
console.log(t.id) // 9193ce3b31332b03f7d8af056c692b84
class User {
private first_name?: string
private last_name?: string
constructor(first_name?: string, last_name?: string) {
this.first_name = first_name
this.last_name = last_name
}
get full_name(): string {
if (
this.first_name == undefined ||
this.first_name == ''
) {
this.first_name = 'John'
}
if (
this.last_name == undefined ||
this.last_name == ''
) {
this.last_name = 'Doe'
}
return `${this.first_name} ${this.last_name}`
}
}
let u = new User()
let u2 = new User('ahmed', 'osama')
console.log(u.full_name) // John Doe
console.log(u2.full_name) // ahmed osama
> Mutating the behaviour of the parent's class method to add/change its implementation
class Parser {
parse(data) {
return Array.from(data)
}
}
class JSONParser extends Parser {
parse(data) {
return JSON.stringify(super.parse(data))
}
}
let p = new Parser()
let jp = new JSONParser()
const first_name = 'ahmed'
console.log(p.parse(first_name))
console.log(jp.parse(first_name))
class Employee {
constructor(
private first_name: string,
private last_name: string,
) {}
}
class Developer extends Employee {
constructor(
first_name: string,
last_name: string,
public salary: number,
) {
super(first_name, last_name)
}
}
index.ts
"use strict";
class Employee {
constructor(first_name, last_name) {
this.first_name = first_name;
this.last_name = last_name;
}
}
class Developer extends Employee {
constructor(first_name, last_name, salary) {
super(first_name, last_name);
this.salary = salary;
}
}
//# sourceMappingURL=index.js.map
index.js
The super call comes first before initializing any properties in a subclass
> Dependency injection is technically when one of your classes uses another class as a property of its own i.e. DI is the technique by which you can compose objects.
Credits to company.ona
class Database {
// Dependency for your models
public query(query: string) {
/*
instance is meant to be your database manager
i.e. the MySQL/pgSQL instance that talks to the database
driver you are working with.
*/
this.instance.execute(query)
}
}
class User {
public constructor(protected db: Database) {}
public register() {
// Some validation logic
this.db.create(/* Validated credentails */)
}
}
Dependency
Client
Uses through an injector
> Only publish what others absolutely need to know.
ProcessÂ
abstraction
DataÂ
abstraction
> More abstraction means less coupling
> Keeping certain parts of your object's state hidden/enclosed within your class and not publishing them
> Not sending user's password within profile data through your API
> Keeping some properties private to the class itself and not publishing them through getters or making them protected
> Enclosing over some algorithm or a class that only provides us with an API to use this functionality
> A remote control that provides you only buttons to turn on/off TV or change channels
> A validator that gives you only exposes to you one function and keeps all other rules and validating process private
abstract class Employee {
constructor(
private firstName: string,
private lastName: string,
) {}
abstract getSalary(): number
get fullName(): string {
return `${this.firstName} ${this.lastName}`
}
compensationStatement(): string {
return `${
this.fullName
} makes ${this.getSalary()} a month.`
}
}
class HR extends Employee {
getSalary(): number {
return 5
}
}
let hr = new HR('ahmed', 'osama')
console.log(hr.compensationStatement())
> An abstract class is more like a blueprint for the classes to follow, and it can have helping members as a regular class
P.S: Abstract classes are not instantiable
interface Employee {
first_name: string
last_name: string
getSalary(): number
compensationStatement(): string
}
class Developer implements Employee {
constructor(
public first_name: string,
public last_name: string,
) {}
getSalary(): number {
return 5
}
get full_name(): string {
return `${this.first_name} ${this.last_name}`
}
compensationStatement(): string {
return `${
this.full_name
} makes ${this.getSalary()} a month.`
}
}
> Interfaces are a lot like abstract classes, they only cannot have methods, so they're absolute blueprints
Can only have public
access modifier
interface Employee {
title: string
getSalary(): number
compensationStatement(): string
}
class Developer implements Employee, Person {
constructor(
public title: string,
public first_name: string,
public last_name: string,
) {}
getSalary(): number {
return 5
}
get full_name(): string {
return `${this.first_name} ${this.last_name}`
}
compensationStatement(): string {
return `${this.title} ${
this.full_name
} makes ${this.getSalary()} a month.`
}
}
let d = new Developer(
'Senior backend dev',
'ahmed',
'osama',
)
console.log(d.compensationStatement())
> Classes can implement multiple interfaces, thus, you can have one class apply multiple purposes or have many shapes
interface Person {
first_name: string
last_name: string
get full_name(): string
}
console.log(d instanceof Developer)
console.log('title' in d) // checking for Employee implementation
console.log('full_name' in d) // checking for Person implementation
Checking if an object implements certain interface
function isPerson(object: object): object is Person {
return (
'first_name' in object &&
'last_name' in object &&
'full_name' in object &&
typeof object['full_name'] === 'function'
)
}
Interfaces can inherit from each other and they support multi inheritance
interface Employee {
title: string
getSalary(): number
compensationStatement(): string
}
interface Person {
first_name: string
last_name: string
get full_name(): string
}
interface Developer extends Person, Employee {
technical_skills: [string, number][]
}
class BackendDeveloper implements Developer {
constructor(
public title: string,
public first_name: string,
public last_name: string,
public technical_skills: [string, number][],
) {}
getSalary(): number {
return 5
}
get full_name(): string {
return `${this.first_name} ${this.last_name}`
}
compensationStatement(): string {
return `${
this.full_name
} makes ${this.getSalary()} per month`
}
}
Interfaces also can be used as type aliases for
variable declarations and parameters in functions/methods
which surely an important feature that we'll use to apply theÂ
SOLID principles and GOF design patterns
interface User {
first_name: string;
last_name: string;
get full_name(): string;
}
let user: User = {
first_name: 'ahmed',
last_name: 'osama',
get full_name() {
return `${this.first_name} ${this.last_name}`;
},
};
> The mechanism by which a class or one of its members may be displayed in many forms.
> The mechanism of bundling data and their modifiers in a single entity (unit)
"Generics" as the name indicates, they are to be thought of as general types but fixed per type check
function reverse<T>(reversable: T) {
if (typeof reversable == 'string')
return reversable.split('').reverse().join('')
else if (Array.isArray(reversable))
return reversable.reverse()
return 'Cannot be reversed'
}
I.E. Generic types can be used for creating more general typed code
Yes.. but we have the "any" type and the "unknown" what makes generics more special?
> Generic types are more like any yes, but they're fixed per type check
They're treated like parameters
for types, so the passed type
will be used as the T type
through the whole executionÂ
of the function/class
Another reason to use generics over any/unknown that you get more IDE support when the compiler infers the type of the passed type
function something<T>(arg: T): T {
return arg
}
let x = something<number[]>([1, 2, 3, 4])
Generic functions/classes... etc. can have multiple generic types, maybe for input and output calculations
function something<T, U>(item: T[]): U[] {
// return something that is array of U
}
Generic constraints are more like type checks for generic types, it would be better explained in code.
function merge<T, U>(o1: T, o2: U) {
return {
...o1,
...o2,
}
}
function merge<T, U>(o1: T, o2: U) {
return {
...o1,
...o2,
}
}
console.log(
merge(
{ x: 1, y: 2 },
{ z: 3, t: 4 }
)
)
function merge<T, U>(o1: T, o2: U) {
return {
...o1,
...o2,
}
}
console.log(
merge(
{ x: 1, y: 2 },
25
)
)
TypeScript doesn't complain, cause actually type U is assignable for number
function merge<T extends object, U extends object>(
o1: T,
o2: U,
) {
return {
...o1,
...o2,
}
}
console.log(
merge(
{ x: 1, y: 2 },
25
)
)
TypeScript now raises an error that the type number is not assignable for object
function extract() {}
// TODO: Implement a function
// that takes an object and a key
// and returns the value of the element
// that has the key in this object
// MUST USE GENERICS
// The following code should return
// exact same values for your function
// to be right
let p = { x: 1, y: 2 }
console.log(extract(p, 'x') === 1) // true
console.log(p.x) // undefined
function extract<T extends object, U extends keyof T>(
dict: T,
key: U,
): T[U] {
let temp = dict[key]
delete dict[key]
return temp
}
This operator checks that the given parameter is assignable to any key that exists with the object on it's right
Notice the return type here, it's quite descriptive that you're returning the property with the key U from the object T and it's completely valid as U is meant to be keyof T
class Shelf<T> {}
class Book {}
class Magazine {}
let s = new Shelf<Book>()
let s2 = new Shelf<Magazine>()
class Collection<T> {
private _items: { [k: string]: T } = {}
public get items(): { [k: string]: T } {
return this._items
}
public get(key: string): T {
return this._items[key]
}
public set(key: string, item: T): void {
this._items[key] = item
}
}
interface Collection<T> {
add(o: T): void;
remove(o: T): void;
}
class List<T> implements Collection<T>{
private items: T[] = [];
add(o: T): void {
this.items.push(o);
}
remove(o: T): void {
let index = this.items.indexOf(o);
if (index > -1) {
this.items.splice(index, 1);
}
}
}
Modules in TypeScript are the same as JavaScript
But, we'll explore them anyway in case you're not familiar with them or need a refresher.
PS: Feel free to skip this topic
> Helps you make your code more portable
> You can make certain parts of your code private and enclosed within the module
> Modules are always evaluated in strict mode
> Modules need a server to be loaded
> Modules are deffered to not block page rendering
> Modules are always evaluated in strict mode
The export keyword is used for sharing some parts of your code outside of your module to be used by other modules
export enum Size {
'small',
'medium',
'large',
}
export enum Color {
'green',
'red',
'blue',
}
Yeah, the opposite of export.
It brings code from other modules into current module
import { Size } from './Size'
import { Color } from './Color'
Surely it exports code, but the default keyword makes it some sort of a special export
that you can import without destructuring
as the name indicates, it's the default export
import sayWelcome from './sayWelcome'
export default function sayWelcome() {
return "hello :D"
}
You can export a huge object from a module
and destructure what you want when importing
or you can import the whole module and alias it
import * as _ from './Module';
console.log(_);
const double = x => x * 2
const square = x => x * x
export {double, square}
Would be the same
if export keyword
is next to consts
Decorators as the name suggests, are modifiers
They can be used to add more functionality to certain parts of your code.
class Component {
@useState<number>(0)
count: number;
setCount: Function;
}
There exist 4 types of decorators you can leverage in typescript
There exist 5 types of decorators you can leverage in typescript
declare type ClassDecorator = <TFunction extends Function>(
target: TFunction,
) => TFunction | void;
const log: ClassDecorator = (target: any): void => {
console.log(target);
};
// function log(target: any): void {
// console.log(target);
// console.log(`Hello from ${target.name}`);
// }
@log
class User {}
@log
class Database {}
Targetted class/function
There exist 5 types of decorators you can leverage in typescript
declare type MethodDecorator = <T>(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>,
) => TypedPropertyDescriptor<T> | void;
function auth(token: string): MethodDecorator {
return function (
target: Object,
prop: string,
desc: PropertyDescriptor,
): void {};
}
class UserController {
@auth('random-jwt-token-maybe')
changePassword() {/* Some process */}
}
Property attributes
[[Value]]: [Function]
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true
Name of the property
declare type PropertyDecorator = (
target: Object,
propertyKey: string | symbol,
) => void;
const logProp: PropertyDecorator = (
target: Object,
prop: string,
) => {
console.log(target, prop);
};
class Playground {
@logProp
private first_name: string = 'ahmed';
}
declare type ParameterDecorator = (
target: Object,
propertyKey: string | symbol,
parameterIndex: number,
) => void;
const logParam: ParameterDecorator = (
target: Object,
key: string,
index: number,
) => {
console.log(key, index);
};
class UserController {
public index(@logParam request) {}
}
class Component {
@useState<number>(0)
count: number;
setCount: Function;
}
class Component {
@useState<number>(0)
count: number;
setCount: Function;
}
function useState<T>(seed: T): PropertyDecorator {
return function (target: Object, key: string): void {
target[key] = seed;
target[
`set${key.replace(/^\w/, (c) => c.toUpperCase())}`
] = (val: T) => (target[key] = val);
};
}
Revisting dependency injection
IoC Containers
So, our goal here is to achieve DI using decorators
Alright, let's start by creating our
Dependency Container
Represents the storage of our dependencies
We need a storage like caching our dependencies
We need a way to extract out an item from cache
We need only one instance from container
class Container<T extends Object> {
private _providers: Map<string, T> = new Map();
get providers() {
return this._providers;
}
}
import { find } from 'lodash';
class Container<T extends Object> {
public resolve(token: string) {
const matchedProvider = find(
this._providers,
(_provider, key) => key === token,
);
if (matchedProvider) {
return matchedProvider;
} else {
throw new Error(`No provider found for ${token}!`);
}
}
}
We need lodash
cause "providers" is object
and we can't easily iterate
over it
Up next, we need to export a single instance of our class a.k.a Singleton
import { find } from 'lodash';
class Container<T extends Object> {
private _providers: Map<string, T> = new Map();
get providers() {
return this._providers;
}
public resolve(token: string) {/*Some logic*/}
}
export const container = new Container();
Now we need to mark classes as prepared for injection a.k.a Injectable
A little hack until we discover the Singleton design pattern
import { container } from './Container';
export function Injectable(token: string): Function {
return function (target: { new () }): void {
container.providers[token] = new target();
};
}
Class decorator
Constructor type
import { container } from './Container';
export function Inject(token: string): PropertyDecorator {
return function (target: any, key: string): void {
Object.defineProperty(target, key, {
get: () => container.resolve(token),
enumerable: true,
configurable: true,
});
};
}
We're either defining a new property
or modifying existing one
import { Inject } from './Inject';
import { Injectable } from './Injectable';
@Injectable('db')
class Database {
sayWelcome() {
return 'hello';
}
}
@Injectable('user')
class User {
@Inject('db') public db;
}
let u = new User(); // no need for constructor
console.log(u.db.sayWelcome()); // hello
import axios from 'axios';
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
class Blog {
posts: Post[] | unknown;
}
let b = new Blog();
axios
.get('https://jsonplaceholder.typicode.com/posts')
.then((res) => {
b.posts = res.data;
console.log(b.posts);
});
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
class Blog {
@Get('https://jsonplaceholder.typicode.com/posts')
posts: Promise<Post[]>;
}
let b = new Blog
b.posts.then(console.log) // API posts
function Get(url: string): PropertyDecorator {
return function (target: Object, prop: string): void {
Object.defineProperty(target, prop, {
get: function () {
return axios.get<Post[]>(url).then((res): Post[] => res.data);
},
});
};
}
What if we want an item with certain id?
function Get(url: string, id?: number): PropertyDecorator {
return function (target: Object, prop: string): void {
Object.defineProperty(target, prop, {
get: function () {
return axios
.get<Post[]>(url)
.then(function ({
data,
}: {
data: Post[];
}): Post[] | Post {
if (id) {
return data.find((item) => item.id === id);
}
return data;
});
},
});
};
}
function Get(url: string, id?: number): PropertyDecorator {
return function (target: Object, prop: string): void {
Object.defineProperty(target, prop, {
get: async (): Promise<Post | Post[]> => {
const { data } = await axios.get<Post[]>(url);
if (id) {
return data.find((item) => item.id === id);
}
return data;
},
});
};
}
What if we want to limit the result?
function Get({
url,
id,
limit,
}: {
url: string;
id?: number;
limit?: number;
}): PropertyDecorator {
return function (target: Object, prop: string): void {
Object.defineProperty(target, prop, {
get: async (): Promise<Post | Post[]> => {
const { data } = await axios.get<Post[]>(url);
if (id) {
return data.find((item) => item.id === id);
}
if (limit) {
return data.slice(0, limit);
}
return data;
},
});
};
}
Object destructuring can be like named parameters from other languages
Raise up your techinal skills
By Security Theater
Ever wondered why the WATs of JS occur? Join me in this journey of understanding JavaScript foundation where we learn things like Type system and Coercion and many more interesting topics.
SecTheater is an online teaching community that targets the IT department. We do our best to produce for you high quality and well-edited screen casts about web development.