type: a set of values and the things you can do with them
- boolean
- the set of all booleans (just
true
/false
) - the operations include
||
,&&
,!
- the set of all booleans (just
- number
- the set of all numbers
- the operations include
+
,*
,/
etc - also includes the methods like
toString
,toFixed
- string
- the set of all strings
- the operations include
+
,||
,&&
- also includes the methods like
toUppercase
andconcat
Knowing something is of type T
tells you 3 things:
- What type it is
- What you can do with it
- What you can NOT do with it
function squareOf(n: number) {
return n * n;
}
squareOf(2); // 4
squareOf('z'); // Error TS2345
// Argument of type '"z"' is not assignable to parameter of type 'number'.
Annotating the parameter of squareOf
allows us to say the following:
squareOf
's parameter is "constrained" to number- The type of the value 2 is assignable to (i.e. compatible with)
number
The type to use as a last resort. It is the set of all values
- Similar to any in the set of values it includes.
- But TypeScript will require checking what it is before using it unsafely
- Must be explicitly set. TypeScript will not infer something as
unknown
- You can compare values of type
unknown
- But you cannot do things that assume an
unknown
value is of a certain
type
without first proving it to TypeScript
let a: unknown = 30; // unknown
let b = a === 123; // boolean
let c = a + 10; // Error TS2571: Object is of type `unknown`
if (typeof a === 'number') {
let d = a + 10; // number
}
- only 2 possible values:
true
&false
- operations include those that compare them & negate them
===
&!
let a = true; // boolean (inferred)
var b = false; // boolean (inferred)
const c = true; // true (inferred specific boolean value)
let d: boolean = true; // boolean (specifically a boolean)
let e: true = true; // true (specific boolean value)
// Error TS2322: type `false`is not assignable to type `true`
let f: true = false; // (specific boolean value)
- type literals: a type that represents a single value and nothing else
- example above for how
e
&f
are set - using
const
(vslet
) will tell TypeScript to set a type literal
(see
Type Widening for why) - powerful feature that adds extra safety
- example above for how
- The set of all numbers:
- integers, floats, positives, negatives,
Infinity
,NaN
, etc - operations include
+
,-
,*
, etc
- integers, floats, positives, negatives,
let a = 1234; // number (inferred)
var b = Infinity * 0.1; // number (inferred)
const c = 5678; // 5678 (inferred as a specific number)
let d = a < b; // boolean
let e: number = 100; // number (explicity)
let f: 26.218 = 26.218; // 26.218 (explicitly set as specific value / type literal)
let g: 26.218 = 10; // Error TS2322: Type '10' not assignable to type '26.218
- The set of all BigInts
- Operations include normal number operations
+
,-
,*
, etc
let a = 1234n; // bigint
const b = 5678n; // 5678n
var c = a + b; // bigint
let d = a < 1235; // boolean
// let e = 88.5n // Error TS1353: a bigint literal must be an integer
let f: bigint = 100n; // bigint
let g: 100n = 100n; // 100n
let h: bigint = 100; // Error TS 2322: Type '100' not assignable to type `bigInt`
As with boolean
& number
, TypeScript will generally do the work of
inferring
the value of bigints, so strive to let it do so
- The set of all strings
- operations include concatenation and the methods like
.slice()
&
.startsWith
let a = 'hello'; // string
var b = 'billy'; // string
const c = '!'; // '!' (type literal)
let d = a + ' ' + b + c; // string
let e: string = 'zoom'; // string
let f: 'john' = 'john'; // 'john'
let g: 'john' = 'zoe'; // Error TS2322: Type "zoe" is not assignable to type "john"
As with boolean
& number
, TypeScript will generally do the work of
inferring
the value of bigints, so strive to let it do so
- Relatively new feature to JavaScript
- Not much can be done with them in regard to operations
let a = Symbol('a'); // symbol
let b: symbol = Symbol('b'); // symbol
var c = a === b; // boolean
let d = a + 'x'; // Error TS2469: The '+' operator cannot be applied to type 'symbol'
Declaring w/ const
and unique symbol
const e = Symbol('e'); // typeof e (inferred as unique symbol)
const f: unique symbol = Symbol('f'); // typeof f
let g: unique symbol = Symbol('f'); // Error TS1332
// A variable whose type is a 'unique symbol' type must be 'const'
let h = e === e; // boolean (unique symbol is always equal to itself)
let i = e === f; // Error TS2367: This condition will always return 'false'
// since the types 'unique symbol' and 'unique symbol' have no overlap
Used to specify the shape of objects
NOTE: TypeScript
object
s cannot tell the difference between simple
objects (declared with {}) and other more complicated ones (i.e.
new Blah
).
This is because JavaScript is Structurally Typed and not Nominal Typed.
Structural Typing: a style of programming where you just care that an
object
has certain properties, and not what its name is (nominal typing)
let a: object = { b: 'x' };
a.b; // Error TS2339: Property 'b' does not exist on type 'object'
Only describing the with object
is not enough. You must also describe the
shape
let a = { b: 'x' }; // { b: string }
a.b; // string
let b = { c: { d: 'f' } }; // { c: { d: string } }
let a: { b: number } = { b: 12 }; // { b: number }
Note: Declaring the above with
const
will infer the type as
{ b: 12 }
,
but will not type it as a type literal. This is because of Type Widening
The following object & class are structurally the same
let c: {
firstName: string;
lastName: string;
} = {
firstName: 'john',
lastName: 'barrowman'
};
class Person {
constructor(public firstName: string, public lastName: string) {}
}
c = new Person('matt', 'smith'); // OK
{firstName: string; lastName: string}
describes the shape of an object. The
object literal & class above both satisfy that shape.
What happens when a property is left out?
let a: { b: number };
a = {}; // Error TS2741:
// Property 'b' is missing in type '{}' but required in type '{ b: number }'
What happens when an extra property is added?
a = { b: 1, c: 2 };
// Error TS2322:
// Type '{ b: number; c: number; }' not assignable to type '{ b: number; }'
// Object literal may only specify known properties, and 'c' does not exist in type '{ b: number; }'
A variable can be declared without being assigned a value. TypeScript will
ensure that the variable is assigned a value before being used.
let i: number;
let j = i * 3; // Error TS2454: Variable 'i' is used before being assigned
let i;
let j = i * 3; // Error TS2532: Object is possibly 'undefined'
let a: {
b: number; // a has a property 'b' that is a number
c?: string; // a might have a property 'c' that is a string
// a may have any number of numeric properties that are boolean
[key: number]: boolean;
};
a = { b: 1 }; // OK
a = { b: 1, c: undefined }; // OK
a = { b: 1, c: 'd' }; // OK
a = { b: 1, 10: true }; // OK
a = { b: 1, 10: true, 20: false }; // OK
a = { 10: true }; // Error TS2741: Property 'b' is missing in type '{ 10: true }'
a = { b: 1, 33: 'red' }; // Error TS2741:
// Type 'string' is not assignable to type 'boolean'
- Used to describe the types of properties that are not known ahead of time
- Tell TypeScript that the given object may contain more keys
- i.e.
For this object, all keys of type T must have values of type U
- The key's type must assignable to either
string
ornumber
- Any word can be used for the key name. Especially if the key is a
string
,
it's best to use a word that describes the value
let airplaneSeatingAssignments: {
[seatNumber: string]: string;
} = {
'34D': 'Boris Cherny',
'34E': 'Bill Gates'
};
- like
const
for object properties 😄
let user: { readonly firstName: string; } = { firstName: 'abby' };
user.firstName; // string
user.firstName = 'abbey with an e'; // Error TS2540: Cannot assign to 'firstName' because it is a read-only property
Every type (except for null
and undefined
) is a subtype of {}
. So avoid using whenever possible.
let danger: {}
danger = {}
danger = {x: 1}
danger = []
danger = 2
// Not ideal at all. So don't do it!
- Object Literal:
{ a: string }
- Empty Object Literal:
{}
---Avoid--- - The
object
type. - The
Object
type. ---Avoid---
TL;DR Use 1 or 3
Be careful to avoid the option 2 & 4. Go to extremes to avoid.
- Use a linter to warn about it
- complain about them in code reviews
- print posters if needed 😆
- Use your team's preferred tool to keep them far away from your code
Value | {} |
object |
Object |
---|---|---|---|
{} |
Yes | Yes | Yes |
['a'] |
Yes | Yes | Yes |
function () {} |
Yes | Yes | Yes |
new String('a') |
Yes | Yes | Yes |
'a' |
Yes | No | Yes |
1 |
Yes | No | Yes |
Symbol('a') |
Yes | No | Yes |
null |
No | No | No |
undefined |
No | No | No |
To declare a type alias that points to a type:
type Age = number
type Person = {
name: string
age: Age
}
- Aliases are never inferred. They must be typed explicitly
- Because
Age
is just an alias for number, it is also assignable to number as shown below
let age = 55
let driver: Person = {
name: 'functionalStoic'
age: age
}
Types cannot be declared twice
type Color = 'red'
type Color = 'blue' // Error TS2300: Duplicate identifier 'Color'
Types are block-scoped just as let
& const
are:
type Color = 'red'
let x = Math.random() < .5
if (x) {
type Color = 'blue' // A new & different `Color` type. Shadows above
let b: Color = 'blue' // no error
} else {
let c: Color = 'red' // uses `Color` type above
}
Type aliases are useful for DRYing up code. They can also be used to increase the clarity of code by using descriptive type names in the same way that descriptive variable names can be useful.
If you have two things, A
& B
:
- The
Union
of them is the sum, or both.Everything in A or B, or Both
- The
Intersection
is whatA
&B
have in common
It is useful to think of them as sets, or as a Venn Diagram
![[Union_And_Intersection.png]]
Symbols used to describe these relationships:
|
(pipe) is used to describeUnion
&
(ampersand) is used to describeIntersection
type Cat = {name: string, purrs: boolean}
type Dog = {name: string, barks: boolean, wags: boolean}
type CatOrDogOrBoth = Cat | Dog
type CatAndDog = Cat & Dog
CatOrDogOrBoth
tells you that an object
has a name
. Otherwise, it can have one or all of the remaining properties
// Cat
let a: CatOrDogOrBoth = {
name: 'Bonkers',
purrs: true
}
// Dog
a = {
name: 'Domino',
barks: true,
wags: true
}
// Both
a = {
name: 'Donkers',
purrs: true,
barks: true,
wags: true
}
Unions are not just one member of the type, it can be any combination. My first impression would point to these being more open that I generally prefer.
CatAndDog
tells you that an object has all 4 properties: name
, purrs
, barks
, wags
let b: CatAndDog = {
name: 'Domino',
purrs: true,
barks: true,
wags: true
}
I don't expect Intersections
will be very common.
function trueOrNull(isTrue: boolean) {
if(isTrue) {
return 'true'
}
return null
}
type Returns = string | null
Another example
function(a: string, b: number) {
return a || b
}
type Returns = string | number
let a = [1,2,3] // number[]
var b = ['a', 'b'] // string[]
let c: string[] = ['a'] // string[]
let d = [1, 'a'] // (string | number)[]
const e = [2, 'b'] // (string | number)[]
let f = ['red'] // string[]
f.push ('blue')
f.push(true) // Error TS2345: Argument of type 'true' is not assignable to parameter of type 'string'
let g = [] // any[]
g.push(1) // number[]
g.push('red') // (string | number)[]
let h: number[] = [] // number[]
h.push(1) // number[]
h.push('red') // Error TS2345: Argument of type 'red' is not assignable to parameter of type 'number'
Two syntaxes are supported for arrays:
T[]
i.e.string[]
Array<T>
i.e.Array<string>
Pro Tip: Items stored in an array should be of the same type.
I generally already do this, but not doing so in Typescript will make it difficult. For example, running map on an array of different types will force checking the type (using
typeof
) before performing an operation on the item.
In most cases, Typescript infers the type of an array. But if an empty array is declared, it will decide the type as the code is executed, but begin as any[]
. In some cases, Typescript will not allow expanding the type further, such as when returned from a function.
All in all, I already use arrays in this way.
- subtypes of [[#Arrays|array]]
- special way to type arrays that have a fixed length
- and where values at each index have specific & known types
- must be explicitly typed at declaration
let a: [number] = [1]
// A tuple of [first name, last name, birth year]
let b: [string, string, number] = ['malcolm', 'gladwell', 1963]
b = ['queen', 'elizabeth', 'ii', 1926] // Error TS2322: Type 'string' is not assignable to type 'number'
Tuple with optional elements:
let trainFares: [number, number?][] = [
[3.75],
[8.25, 7.70],
[10.50]
]
// The above is the same as:
let moreTrainFares: ([number] | [number, number])[] = [
// ...
]
Tuples also support rest
elements:
// A list of strings with at least 1 element
let friends: [string, ...string[]] = ['Sara', 'Tali', 'Chloe', 'Claire']
// A heterogeneous list
let list: [number, boolean, ...string[]] = [1, false, 'a', 'b', 'c']
Pro Tip: Tuples should be used often
They:
- safely encode heterogeneous lists
- capture the length of the list they type
- significantly more safety than a plain array
Typescript has the ability to create an immutable array (I'm excited about this!) with the use of readonly
. This prevents updating the array in place and forces non-mutating methods like .concat
& .slice
instead of mutating methods like .push
& .splice
let as: readonly number[] = [1, 2, 3] // readonly number[]
let bs: readonly number[] = as.concat(4) // readonly number[]
let three = bs[2] // number
as[4] = 5 // Error TS2542: Index signature in type
// 'readonly number[]' only permits reading.
as.push(6) // Error TS2339: Property 'push' does not
// exist on type 'readonly number[]'.
As with standard [[#Arrays]], readonly
arrays can use the longer-form version
type A = readonly string[] // readonly string[]
type B = ReadonlyArray<string> // readonly string[]
type C = Readonly<string[]> // readonly string[]
type D = readonly [number, string] // readonly [number, string]
type E = Readonly<[number, string]> // readonly [number, string]
Typescript has types for null
& undefined
. Each type allows only value
- The
null
type allows the valuenull
- The
undefined
type allows the valueundefined
void
is the return type of a function that doesn't explicitly return anything.
- My comment:
- I generally strive to avoid creating functions that don't return a value due to my preference for functional programming. I doubt I'll use it often, but one example that comes to mind is that Segment Functions (Source & Destination) generally return void
Type | Meaning |
---|---|
null |
Absence of a Value |
undefined |
Variable that has not been assigned a value yet |
void |
Function that doesn't have a return statement |
never |
Function that never returns |
Enums are a way to enumerate the possible values for a type
enum Language {
English,
Spanish,
Russian
}
As a convention, both enum names and keys are both uppercase & singular
Typescript will automatically infer a number as the value for each key if not set explicitly. The above set explicitly is as follows:
const enum Language {
English = 0,
Spanish = 1,
Russian = 2
}
To retrieve the value of an enum, they are accessed in the same way that objects are used:
- dot notation
- bracket notation
const myFirstLanguage = Language.Russian // 2
const mySecondLanguage = Language['English'] // 0
String values can also be used for enums
const enum Color {
Red = '#c10000',
Blue = '#007ac1',
Pink = 0xc10050,
White = 255
}
IMO, Don't use
enum
unless absolutely necessary (and forced to do so)
- For each of these values, what type will TypeScript infer?
let a = 1042 // number
let b = 'apples and oranges' // string
const c = 'pineapples' // string
let d = [true, true, false] // boolean[]
let e = {type: 'ficus'} // { type: string }
let f = [1, false] // (number, boolean)[]
const g = [3] // number[]
// (try this out in your code editor,
// then jump ahead to “Type Widening” if the result surprises you!)
let h = null // null
- Why does each of these throw the error it does?
let i: 3 = 3
i = 4 // Error TS2322: Type '4' is not assignable to type '3'.