TypeScript — Learn the basics 📖
This article is part of the collection “TypeScript Essentials”, this is the Chapter three.
Types? Typing? What is it?
In simple words, types (in web languages):
- helps the compilers to
- optimise the storage of the values in memory — at runtime
- optimise code (mainly for transpilers, ex: Dart) — at compile time - helps developers to know how to use a value (methods, structure, etc)
JavaScript — The dynamic weakly typed
A JavaScript variable can contain any type of data,
JavaScript is dynamically typed, this means that the type of a variable is defined by the type of its data, at runtime.
let a = 1; // a is a number
a = 'ok'; // a is now a string
Though, since it’s weakly typed, you can do “what ever you want” in your expression.
This means that you don’t need to inform the compiler how to convert your values, example :
let a = 1;
let b = "2";
let c = a + b; // "12" -> c is a string
c = a * b; // 2 -> c is a number
The ECMA specification describe how implementations should behave based on values types during an operation (additive, etc):
https://tc39.github.io/ecma262/#sec-additive-operators
This might look much simpler or cool than a * (int) b
,
however this weak typing can introduce some weird type conversion.
const sum = (a, b) => a + b;
sum(1, "2") // "12" -> ??!?!?!
That’s why sometimes you need to “force value type” to avoid runtime unexpected behaviours by using parseInt()
or the + (unary operator)
.
const sum = (a, b) => parseInt(a, 10) + parseInt(b, 10);
sum(1, "2") // 3// Or
const sum = (a, b) => (+a) + (+b);
Then, how to evaluate a variable type in JavaScript ?
- by using
typeof
: typeof is a built-in operator that returns the type of a value by looking in the “type flag” (see MDN article)
Though, beware oftypeof null
bug ⚠️ - by using “duck typing” — With normal typing, suitability is assumed to be determined by an object’s type only. In duck typing, an object’s suitability is determined by the presence of certain methods and properties (with appropriate meaning), rather than the actual type of the object.
➡️ In few words, we “guess” the type looking at the value structure,
like lodash do withisArrayLike()
,isDate()
, … helpers:
https://github.com/lodash/lodash/blob/master/isArrayLike.js#L26-L28
TypeScript —Static Structural typing (with erasure)
TypeScript, as a layer over JavaScript,
add an optional static typing system.
As we’re going to see later, TypeScript bring a lot of more complete type system but some theoretical things must be covered. ⤵️
Structural Typing
Like “duck typing”, TypeScript will look the shape/structure of a type, instead of the type itself.
This is different from C# or Java, and it bring the same flexibility as JavaScript.
type Person = { name: string; };
type Animal = { name: string; kind: string; };
const isPerson(p: Person): p is Person => !!p.name;let a: Animal = { name: 'flocon', kind: 'cat' };
isPerson(a) // true is TypeScript, impossible in C#/Java
More details in the official TypeScript FAQ.
Type erasure
Type erasure means that all types and annotations are removed at transpile time.
Since JavaScript is still dynamically typed, leaving some comments about types in the code would be pointless for V8 or other engine that have their owns optimisation systems.
However, if you look for Reflection, you should definitely subscribe for
Chapter 6 : Make types “real”, the type guard functions.
Write our first types with scalars
The term “scalar” comes from linear algebra, where it is used to differentiate a single number from a vector or matrix.
The meaning in computing is similar.
It distinguishes a single value like an integer or float from a data structure like an array.
⠀
⠀
Use types in TypeScript
It’s really simple, just use a :
suffix to any variable, class property declaration or interface property as below:
const myVar: string = 'hello';type myObjectType = { a: string; b: number; }class MyClass {
myProp: string;
/* ... */
}
ℹ️ Please notice the space after each :
, it’s important to avoid confusion with object property declaration.
The Scalar types
TypeScript define the same scalar types as JavaScript, except it adds a enum
, any
and never
ones:
never
The
never
type represents the type of values that never occur.
Specifically,never
is the return type for functions that never return andnever
is the type of variables under type guards that are never true.
🚨Beware, this is incorrect: let a: never = undefined;
never shows the absence of type, you can see it as the opposite of any
.
any
The variable can contains any type of data, like in JavaScript by default.
let a: any = “hello world”; // valid
let a: any = 1; // valid
let a: any = undefined; // valid
string
let a: string = “hello world”
array
(not a scalar though.)let a: string[] = [“a”, “b”, “c”]
Or tuple :let a: [string, number] = [“1”, 2]
object
(not a scalar though.)
let a: object = {} // correctlet a: object = { a: 1 }
// not correct, we'll see this in next section with "interface"
boolean
let a: boolean = true; // correct
enum
enum Color {Red, Green, Blue}
let c: Color = Color.Green; // (value in JS is a int)
null
let a: null = null; // correct
let a: null = undefined; // incorrect, it should have a null value.
undefined
let a: undefined;
let a?: string; // undefined | string
⚠️ Notice: By default, all types includes null
and undefined
, as says the documentation:
The type checker previously considered
null
andundefined
assignable to anything. Effectively,null
andundefined
were valid values of every type and it wasn’t possible to specifically exclude them (and therefore not possible to detect erroneous use of them).--strictNullChecks
switches to a new strict null checking mode.
In strict null checking mode, thenull
andundefined
values are not in the domain of every type and are only assignable to themselves andany
So, let a: string = undefined;
is correct without the --strictNullChecks
compiler option.
🚨 Beware : do not use String
or Object
types, those are primitive class types.
Rich types: interface and type
The “interface” keyword
The scalar types are essential but not sufficient for “real world” usage.
We need to describe complex data structure as types like objects, classes, etc
TypeScript helps us to describe theses types in a “structural way” — as seen in the first paragraph — with the
interface
keyword.
Interface is used to describe objects, functions or classes.
Describe a User object
interface User {
name: string;
birthday?: string; // optional property
}
As seen in the previous paragraph, the birthday property type is string | undefined
.
Since in JavaScript a object property with undefined value is considered missing, having a nullable type property mean a optional object property.
Describe a Function
interface MyAddFunction {
(a: number, b: number): number
}const add: MyAddFunction = (a, b) => a + b;
Here, the root ()
describe a callable object, in another way, a function.
We’ll see in the next paragraph how to type a function.
Describe an Cat type by re-using Animal type.
Since interfaces are named, you can extends and “re-open” them.
Example:
interface Animal {
name: string;
}interface Cat extends Animal {
mustache_len: number;
}// ---------------------interface A { a: string; }
interface A { b: string; }// is the same as interface A {
a: string;
b: string;
}
The “type” keyword
The type
keyword is used to create “type aliases”, in other words :
- renaming a existing type:
type MyString = string;
Useful for documentation purposes.
- compose existing types : union types, augmenting, …
type PossibleValues = "open" | "close";
const a: PossibleValues = "open";interface Car {
speed: string;
}type CompetitionCar = Car & { competitor_id: string };
We will see more useful example is future articles about advanced types.
What the difference between “interface” and “type”?
Given the following, example, we could say that interface
and type
are the same:
interface A {
a: string;
}type B = { a: string; }
But as explains the official doc and the stack-overflow answer in source, there is some fundamental differences:
interface
can be edited, they are open;type
are closed.type
can compose existing types with union, pick etc
To conclude, we can say that:
type
is especially useful to build intermediate type composed of othersinterface
is useful for build fundamental and re-usable types
Function types, arguments, return type
Declared types, interfaces and variables are finally used
to declare function signatures.
A function signature (or type signature, or method signature) defines input and output of functions or methods.
A signature can include:
- parameters and their types
- a return value and type
- exceptions that might be thrown or passed back
- information about the availability of the method in an object-oriented program (such as the keywordspublic
,static
, orprototype
).
https://developer.mozilla.org/en-US/docs/Glossary/Signature/Function
Let’s type some functions
// inline typing
const capitalize = (str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1);
}// types with interfaces
interface CapitalizeFunction {
(str: string) => string;
}const capitalize: CapitalizeFunction = str => {
return str.charAt(0).toUpperCase() + str.slice(1);
}// function as property in object
interface ReactComponentProps {
onClick: (e: React.MouseEvent<HTMLDivElement>) => void;
}class MyComponent extends React.Component<ReactComponentProps> { }
As you can see, a function type is always using ()
, only return type change depending on the context:
:
for “inline return type — to avoid conflict with arrow function definition=>
for “interface function type”
ℹ️ Please note that “interface function type” is not usable for function
declared function — only for “anonymous functions” aka “arrow functions”.
Object as arguments
Let’s consider this example
interface MyFunctionArgs {
name: string;
security_code: string;
id?: number;
}function isCompleteUser({ name, id = 1 }: MyFunctionArgs): boolean {
return !!name;
}isCompleteUser({ name: 'a', security_code: 'b' });
Here, arguments destructuring is clear, the function only pick name
property.
This notation is particularly useful for compact function signature and optional and named arguments.
I personally find this syntax useful for “data validation” functions.
Conclusion
In this chapter we saw:
- That TypeScript bring clearer variable typing with static typing.
- How the TypeScript typing system behave in a structural and static way.
- That TypeScript bring all fundamental types and even more with nifty ones like
never
orany
.
The next chapter — “Generics, overload” or “Super-types” — will cover more complex cases with more real world examples 👷
Again, thanks to Sylvain PONTOREAU for the review and inspiration.
Feel free to follow me for Chapter five:
Sources:
- http://2ality.com/2013/10/typeof-null.html
- https://stackoverflow.com/questions/964910/is-javascript-an-untyped-language
- https://en.wikipedia.org/wiki/Type_system#Combining_static_and_dynamic_type_checking
- https://github.com/Microsoft/TypeScript/wiki/FAQ#what-is-structural-typing
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
- https://softwareengineering.stackexchange.com/questions/238033/what-does-it-mean-when-data-is-scalar
- https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types
- https://www.typescriptlang.org/docs/handbook/advanced-types.html
- https://www.typescriptlang.org/docs/handbook/basic-types.html