Learn TypeScript w/ Mike North

Game 1: Does it compile?

March 22, 2022

Table of Contents

Example 1

ts
let age = 38
age = Number.NaN
Click here for the answer

Yes, this will compile. Unfortunately, obviously NaN is a number in JavaScript.

ts
let age = 38
age = Number.NaN
Try

Example 2

ts
const vector3: [number, number, number] = [3, 4, 5]
vector3.push(6)
Click here for the answer

Yes, this will compile. Unfortunately, because tuples are a specialized flavor of arrays (and at runtime, they actually are just regular arrays) they expose the entire array API. Look at the type signature of .push()

ts
const vector3: [number, number, number] = [3, 4, 5]
 
vector3.push(6)
Try

Example 3

ts
type Color = {
red: number
green: number
blue: number
}
interface Color {
alpha: number
}
Click here for the answer

No, this will not compile.

ts
type Color = {
Duplicate identifier 'Color'.2300Duplicate identifier 'Color'.
red: number
green: number
blue: number
}
 
interface Color {
Duplicate identifier 'Color'.2300Duplicate identifier 'Color'.
alpha: number
}
Try

Example 4

ts
class Person {
name: string
friends: Person[]
constructor(name: string) {
this.name = name
}
}
Click here for the answer

No, this will not compile.

ts
class Person {
name: string
friends: Person[]
Property 'friends' has no initializer and is not definitely assigned in the constructor.2564Property 'friends' has no initializer and is not definitely assigned in the constructor.
 
constructor(name: string) {
this.name = name
}
}
Try

Example 5

ts
abstract class Person {
public abstract name: string
}
class Student extends Person {
public name: string | string[] = ["Mike North"]
}
Click here for the answer

No, this will not compile.

ts
abstract class Person {
public abstract name: string
}
 
class Student extends Person {
public name: string | string[] = ["Mike North"]
Property 'name' in type 'Student' is not assignable to the same property in base type 'Person'. Type 'string | string[]' is not assignable to type 'string'. Type 'string[]' is not assignable to type 'string'.2416Property 'name' in type 'Student' is not assignable to the same property in base type 'Person'. Type 'string | string[]' is not assignable to type 'string'. Type 'string[]' is not assignable to type 'string'.
}
Try

Example 6

ts
interface Color {
red: number
green: number
blue: number
}
function printColor(color: Color) {
// ... //
}
printColor({
red: 255,
green: 0,
blue: 0,
alpha: 0.4,
})
Click here for the answer

No, this will not compile.

ts
interface Color {
red: number
green: number
blue: number
}
 
function printColor(color: Color) {
// ... //
}
 
printColor({
red: 255,
green: 0,
blue: 0,
alpha: 0.4,
Argument of type '{ red: number; green: number; blue: number; alpha: number; }' is not assignable to parameter of type 'Color'. Object literal may only specify known properties, and 'alpha' does not exist in type 'Color'.2345Argument of type '{ red: number; green: number; blue: number; alpha: number; }' is not assignable to parameter of type 'Color'. Object literal may only specify known properties, and 'alpha' does not exist in type 'Color'.
})
Try

Example 7

ts
type Color = {
red: number
green: number
blue: number
}
class ColorValue implements Color {
constructor(
public red: number,
public green: number,
public blue: number
) {}
}
Click here for the answer

Yes, this will compile.

ts
type Color = {
red: number
green: number
blue: number
}
 
class ColorValue implements Color {
constructor(
public red: number,
public green: number,
public blue: number
) {}
}
Try

Example 8

ts
export class Person {
name: string = ""
}
interface Person {
age?: number
}
Click here for the answer

No, this will NOT compile. When one part of a merged declaration is exported, all other parts must be exported as well.

ts
export class Person {
Individual declarations in merged declaration 'Person' must be all exported or all local.2395Individual declarations in merged declaration 'Person' must be all exported or all local.
name: string = ""
}
 
interface Person {
Individual declarations in merged declaration 'Person' must be all exported or all local.2395Individual declarations in merged declaration 'Person' must be all exported or all local.
age?: number
}
Try

Example 9

ts
class Person {
name: string
constructor(userId: string) {
// Fetch user's name from an API endpoint
fetch(`/api/user-info/${userId}`)
.then((resp) => resp.json())
.then((info) => {
this.name = info.name // set the user's name
})
}
}
Click here for the answer

No, this will NOT compile. The callback passed to .then is not regarded as a “definite assignment”. In fact, all callbacks are treated this way.

ts
class Person {
name: string
Property 'name' has no initializer and is not definitely assigned in the constructor.2564Property 'name' has no initializer and is not definitely assigned in the constructor.
constructor(userId: string) {
// Fetch user's name from an API endpoint
fetch(`/api/user-info/${userId}`)
.then((resp) => resp.json())
.then((info) => {
this.name = info.name // set the user's name
})
}
}
Try

Example 10

ts
enum Language {
TypeScript = "TS",
JavaScript,
}
enum Editor {
SublimeText,
VSCode = "vscode",
}
enum Linter {
ESLint,
TSLint = "tslint",
JSLint = 3,
JSHint,
}
Click here for the answer

No, this will NOT compile, but it’s probably more nuanced than you expected!

Once you provide a string initializer for an enum member, all following enum members need an explicit initializer of some sort, unless you go back to numeric enum values, at which point inference takes over again.

Ok, this one wasn’t really fair :)

ts
enum Language {
TypeScript = "TS",
JavaScript,
Enum member must have initializer.1061Enum member must have initializer.
}
 
enum Editor {
SublimeText,
VSCode = "vscode",
}
 
enum Linter {
ESLint,
TSLint = "tslint",
JSLint = 3,
JSHint,
}
Try

Example 11

ts
function handleClick(evt: Event) {
const $element = evt.target as HTMLInputElement
if (this.value !== "") {
this.value = this.value.toUpperCase()
}
}
Click here for the answer

No, this will NOT compile. When you have a free-standing function like this, and refer to the this value, we need to give it a type of some sort.

ts
function handleClick(evt: Event) {
const $element = evt.target as HTMLInputElement
if (this.value !== "") {
'this' implicitly has type 'any' because it does not have a type annotation.2683'this' implicitly has type 'any' because it does not have a type annotation.
this.value = this.value.toUpperCase()
'this' implicitly has type 'any' because it does not have a type annotation.
'this' implicitly has type 'any' because it does not have a type annotation.
2683
2683
'this' implicitly has type 'any' because it does not have a type annotation.
'this' implicitly has type 'any' because it does not have a type annotation.
}
}
Try

Here’s a version that would compile

ts
function handleClick(this: HTMLInputElement, evt: Event) {
const $element = evt.target as HTMLInputElement
if (this.value !== "") {
this.value = this.value.toUpperCase()
}
}
Try

Example 12

ts
class Person {
#name: string
private age: number
constructor(name: string, age: number) {
this.#name = name
this.age = age
}
}
class Student extends Person {
#name: string | string[]
private age: number
constructor(name: string, age: number | null) {
super(name, age || 0)
this.#name = name
this.age = age
}
}
Click here for the answer

No, this will NOT compile. Because TS private fields are just “checked for access at build time” and are totally accessible outside the class at runtime, there’s a collision between the two age members.

As a result Student is not a valid subclass of Person

ts
class Person {
#name: string
private age: number
constructor(name: string, age: number) {
this.#name = name
this.age = age
}
}
 
class Student extends Person {
Class 'Student' incorrectly extends base class 'Person'. Types have separate declarations of a private property 'age'.2415Class 'Student' incorrectly extends base class 'Person'. Types have separate declarations of a private property 'age'.
#name: string | string[]
private age: number
 
constructor(name: string, age: number | null) {
super(name, age || 0)
this.#name = name
this.age = age
Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.2322Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.
}
}
Try

Example 13

ts
class Person {
#name: string
constructor(name: string) {
this.#name = name
}
}
function makeName(name: string | string[]): string {
if (Array.isArray(name)) return name.join(" ")
else return name
}
class Student extends Person {
#name: string | string[]
constructor(name: string | string[]) {
super(makeName(name))
this.#name = name
}
}
Click here for the answer

Yes, this will compile. Because Ecma #private fields are not visible, even at runtime, outside of the class, there’s no collision between the two #name members.

ts
class Person {
#name: string
constructor(name: string) {
this.#name = name
}
}
 
function makeName(name: string | string[]): string {
if (Array.isArray(name)) return name.join(" ")
else return name
}
 
class Student extends Person {
#name: string | string[]
 
constructor(name: string | string[]) {
super(makeName(name))
this.#name = name
}
}
Try


© 2022 All Rights Reserved