Categories
TypeScript

Using Static Types in TypeScript

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at the use of static types in TypeScript projects.

Static Types

TypeScript types differ from JbaScript types. We can specify many more data types than what JavaScript allows.

JavaScript is dynamically typed. This is an obstacle for programming familiar with other languages.

Values have types instead of variables in JavaScript.

For instance, if we have:

let x = 1;

Then we can get the type of the value of x which is 1, with the typeof operator.

For instance, we can write:

typeof x

and we would get 'number' .

If we don’t assign a value to something then its type would be undefined .

A value with type undefined can only be undefined .

JavaScript only has the following built-in types.

  • number — the data type for representing numeric values
  • string — the data type for representing text data
  • boolean — true or false
  • symbol —the data type for representing unique constant values
  • null — the value null , used for indicating a nonexistent or invalid reference
  • undefined — the data type used when a variable has been defined but haven’t been assigned a value
  • object — represents compound values.

Variables can be assigned anything with any of these types.

Function parameter types are also dynamic. This means that parameters can receive any kind of data.

Therefore, to make our lives easier, we can use TypeScript to help us define some types.

Creating a Static Type with a Type Annotation

We can create a static type with type annotations with TypeScript.

The most basic data types in TypeScript are static types.

We can annotate variables and parameters with data type annotations to make our assumptions explicit.

Also, we can do the same for parameters.

For instance, we can write:

const add = (a: number, b: number): number => {
  return a + b;
};

We have the add function which has the parameters a and b , both of which are numbers.

We also return a number after adding them together.

This is the most basic kind of type annotation and it’s useful to prevent making mistakes.

If we pass in anything that isn’t a number, we’ll get errors from the TypeScript compiler.

Likewise, we can add type annotations to variables.

For instance, we can write:

let price: number = 100;

to restrict price to only be assigned numbers.

Implicitly Defined Static Types

If the type is obvious from the value assigned, then we don’t have write the data type annotation explicitly.

For instance, if we have the example above:

let price: number = 100;

We can remove the type annotation and write:

let price = 100;

The TypeScript compiler will infer the type automatically.

Likewise, we can do the same for return types.

For instance, we can write:

const getTax = (price: number) => {
  return (price * 0.2).toFixed(2);
};

const halfTax = getTax(100) / 2;
console.log(tax);

We’ll get the error ‘The left-hand side of an arithmetic operation must be of type ‘any’, ‘number’, ‘bigint’ or an enum type.ts(2362)’ with the second last line.

This is because the TypeScript compiler knows that we can only divide a number by 2.

If our code has no compiler errors, then the compiler builds the code when we run it.

Photo by Mikhail Vasilyev on Unsplash

any Type

TypeScript doesn’t stop us from being flexible with data types.

It provides us with the any time to bypass the data types checks done by the compiler.

However, it does stop us from using it accidentally.

We can change getTax to:

const getTax = (price: any): any => {
  return (price * 0.2).toFixed(2);
};

so that we return anything we want and accept any kind of parameter we want.

However, we shouldn’t use any too much so that we can take advantage of the benefits of TypeScript.

Conclusion

We can use static data type for basic data type annotations.

TypeScript will also do type inference for obvious situations so we don’t have to always add data type annotations.

Also, it has the any type to let us assign anything to a variable, parameter, or return anything, but we shouldn’t use it too much.

Categories
TypeScript

JavaScript Object Features in TypeScript — Modules

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to define and use modules.

Using Modules

Almost all apps can’t be contained in a single file.

Therefore, we need to put our code in modules so that we can organize the code.

This way, we can break our code into manageable chunks.

JavaScript modules can be used with TypeScript projects.

And they should be used since they’re now standard in JavaScript.

Node.js also has support for JavaScript modules since version 12, so we can use them without adding any transpilation in Node projects as well.

Creating a JavaScript Module

To create a module, we just have to create a JavaScript file.

Then if we want to make a member be importable, then we put the member at the top level and use the export keyword to expose it to the outside.

For instance, we can write:

export const name = "joe";

This will make our name variable be importable from another module.

This kind of export is called a named export since we’ve to specify the name of the member explicitly when we import it.

We can also do a default export.

To do that, we can use the default keyword:

export default {
  name: "james"
};

We can only have default export in any module.

We can name it anything when we import this in another module.

Using a JavaScript Module

To use an export member of a module, we’ve to import it.

For instance, if we want to use the following exported member:

export const name = "joe";

We can write:

import { name } from "./module";

console.log(name);

We used the import keyword with the member of the module in curly braces to import it.

Then we can reference it anywhere inside the module.

To import a default export, we skip the curly braces.

For instance, if we have:

export default {
  name: "james"
};

Then we can import the object by writing:

import obj from "./module";

console.log(obj.name);

We skipped the curly braces in the import statement and log the value after the import.

The ./ tells us that we’re searching for a path relative to the current module.

So we should include it for relative imports.

If we skip the ./ , then we indicate that we’re importing from a dependency rather than a module in the local project.

The most common location for module dependencies would be in the node_modules folder.

Defining Multiple Named Members in a Module

We can define multiple named members in a module.

For instance, we can write:

export const name = "joe";
export const age = 20;

We exported the members name and age from the module.

Then we import then by writing:

import { name, age } from "./module";
console.log(name, age);

We then import them by separating them with a comma.

With named members, we can selectively import the members that we want.

This way, we don’t have to import things that we don’t use.

Conclusion

In a TypeScript project, we’ve to use modules to make them organized into small, manageable chunks.

We can just import and export members as we wish.

And we don’t have to expose everything to the outside or import everything.

Categories
TypeScript

JavaScript Features that we can use in TypeScript Code

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at JavaScript features that we can adopt in our JavaScript code.

Using Arrow Functions

Arrow functions are a way to concisely define functions.

They’re often used to define functions that are arguments of other functions.

In JavaScript, functions are regular objects, so they can be passed into another function as arguments.

For instance, we have an example below:

const addPrices = (...rest) => {
  return rest.reduce((total, b) => total + b, 0);
};

In the code above, the outer function is a function.

We also passed a function into the reduce method.

There are 3 parts to an arrow function.

We have the input parameters, the equal sign, the great than sign, and the result value.

return and the curly braces are only required only if the arrow functions need to run more than one statement.

Arrow functions can be used anywhere that a function is used.

Working with Arrays

JavaScript arrays are similar to the ones that are in other programming languages.

However, they can be dynamically resized and can contain any combination of values.

This means that they can be in any combination of types.

The size of an array isn’t specified when it’s created and it’ll be allocated automatically as items are added or removed.

JavaScript arrays are zero-based and are defined using square brackets.

It can have initial contents separated by commas.

For instance, we can write:

const names = ['joe', 'jane', 'alex'];

Then we create an array with all strings.

Also, we elements in an array can be read or set using the square brackets in using some array methods.

It has a concat method which takes one or more arrays as arguments to combine it with another array.

join takes a separator string that combines the entries into a string and returns it, with each value separated by the separator .

pop removes and returns the last item of an array.

shift removes and returns the first element in the array.

push takes an item as an argument and add it to the end of the array in-place.

unshiift takes an item and add it to the start of an array.

reverse returns a new array with the items of the original array reversed.

slice(start, end) returns a second of an array.

sort sorts an array optionally with a comparison function for custom sorting.

splice(index, count) removes an item from the index and up to the count .

every(test) returns true if the test function returns true when it’s called with all values of an array.

some(test) returns true if the test function returns true when it’s called with some entries of the array.

filter(test) returns an array with the items that returns true when they’re called with test .

find(test) returns the first instance of something that returns true when called with test .

findIndex(test) returns the index of the first instance of something that returns true when called with test .

forEach(callback) loops through the and calls callback on each item.

includes(value) returns true if value is inside the array.

map(callback) returns a new array that has the results of invoking callback for all items in the array.

reduce(callback) returns an accumulated value produced by running the callback for every item of the array.

Spread Operator

Spread operator can be used on arrays in various ways.

We can use them for spreading array entries as arguments into a function call.

For instance, we can write:

const addPrices = (...rest) => {
  return rest.reduce((a, b) => a + b, 0);
};

const prices = [1, 2];
const totalPrice = addPrices(...prices);

Then we spread the prices array as arguments into the addPrices function.

Conclusion

Arrow functions are useful in our TypeScript code.

Array methods are also very useful for finding things and manipulating items.

There are also methods to manipulate methods in place, like adding and remove items.

The spread operator to spread items that are in an array into arguments of a function.

Categories
TypeScript

TypeScript’s Relation to JavaScript

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how TypeScript makes JavaScript projects better.

Type Coercion

JavaScript does data type coercion of its variables when it does certain operations.

It produces consistent results, we just have to know how it works.

If we use the == operator to compare objects, then type coercion will be done before comparison operations are done.

For instance, we may have:

let applePrice = 1;
let orangePrice = '1';
if (applePrice == orangePrice) {
  //...
}

Then both will be converted to numbers before comparison.

When we write:

let totalPrice = applePrice + orangePrice;

then both will be converted to strings before concatenation, which is probably not what we want.

Avoiding Unintentional Type Coercion

To make our lives easier, we should take steps to avoid unintentional data type coercion.

To do that, we can use the === operator for comparisons and convert types explicitly first before doing concatenation.

For instance, we can write:

let applePrice = 1;
let orangePrice = '1';
if (applePrice === orangePrice) {
  //...
}

for comparison, and:

let totalPrice = Number(applePrice) + Number(orangePrice);

=== does comparison without applying data type coercion.

And we prevent concatenation with the + sign by using the Number function to convert both operands to numbers before adding them.

The Benefits of Explicit Type Coercion

We can use type coercion to our benefit.

For instance, we can use the || to coerce null , undefined or other falsy values to false so that the || operator will return the 2nd operand.

For instance, if we have:

let firstName;
let secondName = firstName || "jane";

Since firstName is undefined , then || operator will coerce firstName to false and return the 2nd operand, which is 'jane' .

Therefore, we can use the || operator to return a default value inc case the first is falsy.

Working with Functions

Functions are a building block of JavaScript.

We can define functions to run code that’s invoked repeatedly.

For instance, we can write:

const addPrices = (first, second) => {
  return first + second;
};

Then we can call it by writing:

let applePrice = 1;
let orangePrice = 2;
const totalPrice = addPrices(applePrice, orangePrice);

The addPrices function received number values when we call it above.

However, JavaScript doesn’t do any validation when we pass in the variables to the function.

Therefore, we may get unexpected results.

This is where TypeScript can help, we can validate the data types before the function is called when the arguments are passed in.

Function Results

In JavaScript, function return types are determined by the value we return.

It can be anything, depending on what we return.

For instance, if we have:

let applePrice = 1;
let orangePrice = 2;
const totalPrice = addPrices(applePrice, orangePrice);

then we return a number.

On the other hand, if we have:

let applePrice = 1;
let orangePrice = '2';
const totalPrice = addPrices(applePrice, orangePrice);

Then addPrices returns a string.

This may be a problem since we probably don’t want to concatenate instead of add.

Only 2 numbers are added together. Otherwise, it’s concatenated.

Also, we don’t have to pass in all arguments.

For instance, if we add the 3rd argument to addPrices as follows:

const addPrices = (first, second, third) => {
  return first + second + third;
};

let applePrice = 1;
let orangePrice = 2;
const totalPrice = addPrices(applePrice, orangePrice);

Then we get NaN because third is undefined , and adding number and undefined results in NaN .

Avoiding Argument Mismatch Problems

In JavaScript, we can set a default value for a parameter that we know are optional.

For instance, we can write:

const addPrices = (first, second, third = 0) => {
  return first + second + third;
};

or, we can use the rest parameter notation to put some or all arguments into an array.

If third has no value passed in, then third is set to 0.

We can write:

const addPrices = (...rest) => {
  return rest.reduce((a, b) => a + b, 0);
};

Then rest is an array as the spread operator puts all the arguments into an array.

Conclusion

TypeScript just uses JavaScript’s type system.

It tames the dynamic nature of JavaScript’s type system by restricting the types of parameters and return types of functions.

Categories
TypeScript

TypeScript Project Setup— Modules and Options

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to use modules in a TypeScript project.

Also, we look at the options we can set for the TypeScript compiler

Module Formats

Before ES6, we have different kinds of JavaScript modules to solve the problems with organizing code into small pieces.

There were multiple types of modules before that.

TypeScript has built-in support for standard JavaScript modules.

So we can use the export and import syntax for exposing module members and using them respectively.

For instance, we can export a member by writing:

module.ts

export const printMsg = (msg: string): void => {  
  console.log(msg);  
};

Then we can use it in another module by importing it.

The export keyword lets us export a member in a module.

We can export anything at the top level.

Then we can use our module by using the import keyword.

For example, we can write:

import { printMsg } from "./module";  
printMsg("foo");

We import the printMsg function with the import statement.

Then we called it.

We should get 'foo' logged in the console log just like when we called it in the same module.

If we build the code, then they’ll be bundled together.

And the code will vary depending on the target that we choose.

We won’t get any references to modules in our build artifacts.

If we’re using a standalone TypeScript project, then we’ve to specify in the lib section of tsconfig.json .

We should specify the module system that we want to compile with the Typescript compiler with the module setting.

The following choices can be set for this setting.

none will disable modules.

commonjs selects the CommonJS module format which is supported by Node

amd selected Asynchronpus Module Definition, supported by the RequireJs module loader.

system selects the module format supported by the SystemJS module folder.

umd selected the Universal Module Definition module format.

es2015 or es6 selects the module format in the ES6 language specification.

esnext selects modules features that have been proposed for the next version of the JavaScript language.

To add the option, we can write:

{    
  "compilerOptions": {    
    "target": "es2018"  
    //...  
    "module": "commonjs"  
  }  
}

Once we have that, we can use the module system of our choice.

We can use commonjs for Node apps, but es6 is the standard so we should use that.

Modules are usually located in the node_modules folder.

TypeScript compiler uses the classic resolution with module is set to es2015 , system , or amd .

The resolution style can be set using the moduleResolution configuration property in tsconfig.json using the classic or node value.

Compiler Configuration Settings

There are many other compiler configuration options in addition to the module and target settings.

allowJs sets whether we wan to include JavaScript files in the compilation process.

allowSyntheticDefaultImports allows imports from modules that don’t declare a default export.

It’s used to increase code compatibility.

baseUrl specifies the root location to resolve module dependencies.

checkJs tells the compiler to check JavaScript code for common errors.

declaration is the option we set to produce type declaration files that provide type information for JavaScript code.

downlevelIteration enables support for iterators when targeting an older version of JavaScript.

emitDecoratorMetadata option lets us include decorator metadata in the JavaScrti emitted by the compiler and is used with the experimentalDecorations option.

experimentalDecorators enables support for decorators.

forceConsistentCasingInFileNames enures the names of import statements match the case used by the imported file.

importHelpers determines whether the helper code is added to JavaScript to reduce the amount of code that’s produced overall.

isolateModules treats each file as a separate module.

jsx specifies how HTML elements in JSX/TSX files are processed.

jsxFactory specifies the name of the factory function that’s used to replace HTML files in JSX/TSX files.

noImplicitAny prevents implicit use of the any type.

noImplicitReturns option requires all paths in a function to return a result.

resolveJsonModule allows us to import JSON files as if they’re modules.

skipLibCheck lets us skip the checks for declaration files.

strict enables stricter checking of TypeScript code.

strictNullChecks prevents null and undefined from being accepted as values for other types.

suppressExcessPropertyErrors stops the compiler from generating errors for objects that defined properties, not in the specified range.

typeRoots specifies the root location the compiler uses to look for declaration files.

types specifies a list of declaration files to include when compiling.

Conclusion

With the TypeScript compiler, we can specify lots of options to control how code is converted from TypeScript to JavaScript code we can use.

Modules should be used in our project, but we’ve to specify the kind of modules we want to build first.