Categories
TypeScript Best Practices

TypeScript Best Practices — Member Access, Loops, and Function Types

TypeScript is an easy to learn extension of JavaScript. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust TypeScript code.

In this article, we’ll look at the best practices to following when writing code with TypeScript, including disallowing member access on any typed variables.

Also, we look at why we want to use const assertions.

We also look at why import should be used instead of require for importing modules.

We find out how we can use interfaces to specify the type of functions.

And we look at why the for-of loop is better than the regular for loop.

No Member Access on any Typed Variables

any may leak into our codebase in nested entities.

For instance, we may have type declarations that have the any type.

Therefore, if we allow those nested members to be accessed, we may run into type errors at runtime.

So instead of writing:

declare const anyObj: { prop: any };
anyObj.prop.a.b;

We should write:

declare const anyObj: { prop: string };
anyObj.prop;

Now that we know anyObj.prop must be a string, we can access it safely.

Don’t Return any From a Function

We shouldn’t return anything with the any type in a function.

Instead, we should add a return type annotation so that we know what it’s returning.

For instance, instead of writing:

function foo() {
  return 1 as any;
}

or:

function arr() {
  return [] as any[];
}

We should write:

function arr(): number[] {
  return [1, 2];
}

or:

function foo(): number {
  return 1;
}

Now we know what the functions return.

No Unused Variables and Arguments

Unused variables and arguments are useless, so we probably shouldn’t have them in our code.

We can remove variable declarations like var , const or let variables that aren’t used.

Likewise, functions and classes that aren’t used can be removed.

enums, interface, and type declarations that aren’t used can also be removed.

Class members like methods, instance variables, parameters can all be removed if they aren’t used.

This also applies to import statements.

No requires Statements Except in Import Statements

We shouldn’t use require statements any more since ES6 modules have become standard.

Therefore, instead of writing:

const foo = require('foo');

We write:

import foo = require('foo');

or:

import foo from 'foo';

Use as const Over Litreral Types

const assertions are better than literal types since they make values constant.

If we use const assertions, literal types can’t be widened, and object and array entries become readonly .

For instance, instead of writing:

let bar: 2 = 2;

or:

let bar = { bar: 'baz' as 'baz' };

We can write:

let foo = 'bar' as const;

or:

let foo = { bar: 'baz' };

Use for-of Loop Instead of a for Loop

for-of loops let us loop through items with a simple loop instead of having to set up looping conditions and index variables.

It also works with any kind of iterable object instead of objects with index and the length property.

For instance, instead of writing:

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

We write:

for (const x of arr) {
  console.log(x);
}

As we can see, the for-of loop is much simpler if we just want to loop through all items in an iterable object.

Use Function Types Instead of Interfaces with Call Signatures

We can use function types instead of interfaces or object type literals with a single call signature.

For instance, instead of writing:

function foo(bar: { (): number }): number {
  return bar();
}

We write:

interface Foo {
  (): void;
  bar: number;
}

const foo: Foo = () => {};
foo.bar = 1;
foo();

We have a function that returns nothing and has a bar property that’s a number.

Conclusion

We may want to use as const over literal types to prevent the type of the variable from widening and to make them, read-only.

Also, we want to use import instead of require for importing modules.

We also want to use interfaces instead of functions to specify the type of functions.

Finally, the for-of loop is better than the for loop in most cases.

Categories
TypeScript Best Practices

TypeScript Best Practices — Semicolons and Spacing

TypeScript is an easy to learn extension of JavaScript. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust TypeScript code.

In this article, we’ll look at the best practices to following when writing code with TypeScript, including removing extra semicolons.

Also, we look at some spacing conventions and why we should put semicolons in our TypeScript code.

Remove Useless Semicolons

We should remove useless semicolons in our code.

For instance, instead of writing:

let x = 5;;

We write:

let x = 5;

We only need one semicolon at the end of a statement.

Replace Magic Numbers with Named Constants

If we have numbers that are used as constants repeatedly but aren’t assigned to a constant, then we should assign it to one.

This way, we can change it once and the value will be reflected everywhere.

Also, named constants tell us the meaning of the number.

Therefore, instead of writing:

enum Foo { bar = 1 }

We write:

const NUMBER_OF_BARS = 1;
enum Foo { bar = NUMBER_OF_BARS }

No Unused Expressions

We shouldn’t have unused expressions in our code.

If we have them, we should remove them.

No Unused Variables

Likewise, if we have unused variables, then we should remove them.

Don’t Use Variables Before they’re Defined

With variables that are declared with var , we can reference the variable before they’re defined, but the value will be undefined .

This is because the variable is hoisted.

let and const solve this problem since they aren’t hoisted.

Therefore, we should use let or const variables.

This way, if we reference those variables, we’ll get an error.

No Useless Constructors

We shouldn’t have useless constructors.

They include:

class A {
  constructor () {
  }
}

or:

class B extends A {
  constructor (value) {
    super(value);
  }
}

They’re both redundant so they should be removed.

Have Consistent Use of Backticks, Double Quotes or Single Quotes

We should use backticks or quotes in a consistent manner for declaring strings.

Better yet, we should use backticks since they’ll create template strings, which are more flexible.

They allow expressions to be embedded in it.

Don’t Use async if await isn’t Used Inside the Function

We should use async fucntions only is we have to await something inside it.

For instance, if we have something like:

const foo = async () => "bar";

then we should use a normal function.

Return Awaited Values Consistently

We should have return and await on the same line since the promises may not have resolved yet.

Instead, put them on separate lines.

The only exception of this is that we can put return and await on the same inside a try block to catch errors from another promised-based fucnction.

For instance, instead of writing:

async function foo() {
  return await bar();
}

We write:

async function foo() {
  const val = await bar();
  return val;
}

or:

async function foo() {
  try {
    return await bar();
  } catch (error) {}
}

Require Semicolons Instead of Automatic Semicolon Insertion

Instead of letting the Javascript interpreter put in semicolons for us, we should put them in ourselves.

Therefore, we should write:

return {
    name: "foo"
};

instead of:

return
{
    name: "foo"
};

which is the same as:

return;
{
    name: "foo"
};

Have Consistent Spacing Before Function Parenthesis

We should add consistent spacing before function parenthesis.

For instance, we usually write:

function foo(x) {
  // ...
}

in JavaScript code.

Conclusion

We should have consistent spacing in our TypeScript code for better readability.

Also, we should put in semicolons in our code instead of letting the JavaScript interpreter add them for us in unexpected places.

Duplicate semicolons should be removed.

Categories
TypeScript Best Practices

TypeScript Best Practices — Spacing, Arrays, and Class Members

TypeScript is an easy to learn extension of JavaScript. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust TypeScript code.

In this article, we’ll look at the best practices to following when writing code with TypeScript, including having consistent brace styles and spacing.

Also, we should have only one member with a given name in a class.

And we should also avoid the array constructor.

Useless parentheses should also b removed.

We Should Have Consistent Brace Styles

We should keep consistent brace styles for anything that needs to be wrapped in braces.

For instance, if we have an interface, we can write:

interface Foo {
  name: string;
}

This also applies to classes, enums, type aliases, and any other blocks.

Have Consistent Commas Spacing

Comma spacing in parameter lists should be consistent.

For instance, we can write:

interface Foo {
  foo(val: string, bar: string): string;
}

We have space after the comma in the list.

Default Parameters Should be the Last Parameter

We can place default parameters anywhere in the function signature in JavaScript.

Therefore, to be consistent, we should place them last so that we don’t have to look at the definition to know where they are.

For instance, instead of writing:

function f(a = 0, b: number) {}

We write:

function f(a, b: number = 0) {}

Remove Spacing Between Function Identifiers and their Invocations

We should remove spaces between function names and the opening parenthesis.

For instance, instead of writing:

alert ('hi');

We write:

alert('hi');

Have Consistent Indentation within Our Code

2 spaces for indentations is great because they’re the same in all operating systems.

We can save typing by converting tabs to 2 spaces.

Also, we should have a space between operands.

For instance, instead of writing:

if (a) {
 b=c;
}

We write:

if (a) {
  b = c;
}

Initialization in Variable Declarations

We can have initialization in variable declarations.

For instance, we can either write:

let x;

or:

let x = 1;

They’re both fine in our code.

Spacing Before and After Keywords

We should have space after most keywords.

For instance, we can put a space after if , for , while , do , else , switch , case , or new .

We can write:

if (bar) {
    _// ..._
} else {
    _// ..._
}

or:

for (const a of arr) {
  //..
}

or:

while (foo){
  //...
}

or:

do {
  //...
} while (foo)

or:

switch (foo){
  case 1: {
    //...
    break;
  }
  //...
}

One space makes our code more readable.

Don’t Use the Array Constructor

The Array constructor isn’t very useful.

Also, there are 2 versions of it.

If we pass in 1 argument, then we get an array with the number of empty entries we passed into the constructor.

On the other hand, if we pass in multiple arguments, then it returns an array with all the arguments we passed in.

To make our lives easier, we should use array literals.

For instance, instead of writing:”

const arr = Array(1);

or:

const arr = new Array(1);

or:

const arr = new Array(1, 2, 3);

We write:

const arr = [1, 2, 3];

No Duplicate Class Members

We shouldn’t have 2 class members with the same name since the 2nd one will overwrite the first one.

For instance, instead of writing:

class Foo {
  bar() { return 1; }
  bar() { return 2; }
}

We write:

class Foo {
  bar() { return 1; }
}

or:

class Foo {
  bar() { return 2; }
}

No Empty Functions

Empty functions are useless.

Therefore, we shouldn’t write them.

Instead of writing:

functuion foo(){}

We should remove it.

No Useless Parentheses

Some parentheses are useless and others are useful for separating code.

Some parentheses that are useless include:

for (const a in (b)){
  //...
}

or:

for (const a of (b)){
  //...
}

Instead, we should write:

for (const a in b){
  //...
}

or:

for (const a of b){
  //...
}

Also, the parentheses in typeof (a); isn’t very useful.

Instead, we should write typeof a;

Conclusion

We should have consistent spacing in our code.

Also, we shouldn’t have duplicate class members.

The array constructor should also be avoided.

Useless parentheses should also b removed.

Categories
TypeScript Best Practices

TypeScript Best Practices — Slash Directives, Types, and Unbound Methods

TypeScript is an easy to learn extension of JavaScript. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust TypeScript code.

In this article, we’ll look at the best practices to following when writing code with TypeScript, including stopping the use of triple-slash directives.

Also, we should merge function overloads that can be merged.

We look at why unbound methods should be called within their scope.

And we look at spacing and adding type annotations everywhere.

Stop Using Triple Slash Directives in Favor of ES6 Modules

Now that JavaScript has modules as a standard feature, we don’t have to use triple-slash directives to access TypeScript namespaces and modules anymore.

For instance, instead of writing:

/// <reference path="foo" />

We should write:

import foo from './foo';

or:

import { bar } from './foo';

Require Consistent Spacing Around Type Annotations

We should have consistent spacing around data type annotations for improved readability.

For instance, we can write:

let foo: string = "bar";

The code above is readable.

Require Type Annotations to Exist

We should add data type annotations wherever we can.

This way, we get type checks and autocomplete everywhere in our project.

For instance, we should write:

class ContainsText {
  text: string;
  bar: string = 'text';
}

We have data type annotations to label the types of variables and have them in variables that have a value assigned to them for consistency.

Enforce Unbound Methods are Called within their Expected Scope

We should make sure that a method isn’t used outside of their expected scope.

For instance, if we have:

class Foo {
  public log(): void {
    console.log(this);
  }
}

Then we shouldn’t call log as follows:

const foo = new Foo();
const log = instance.log;
log();

Since log is now at the top level, this would be the global object, which is window in the browser and global in Node.

Instead, we should call it by writing:

const foo = new Foo();
foo.log();

If Overloads can be Unified into One by a Union or Optional Parameter, then we should Merge them Together

If our functions have overloads that can be merged together, then we should do so.

For instance, instead of writing:

function f(x: number): void;
function f(x: string): void;

We should write:

function f(x: number|string): void;

And if we have:

f(): void;
f(...arr: number[]): void;

Then we should write:

function f(arr?: ...number[]): void;

This way, we reduce the number of declarations that are needed.

Conclusion

We should reduce the number of functions declarations that are needed by merging overloads that can be merged together.

Also, we should make sure that anything that references this are called within their expected scope.

If we’re still using triple-slash directives, then we should turn them to module imports wherever we can.

Categories
TypeScript Best Practices

TypeScript Best Practices — String Search, Union Styles, and More

TypeScript is an easy to learn extension of JavaScript. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust TypeScript code.

In this article, we’ll look at the best practices to following when writing code with TypeScript, including better ways to check the start and end of strings.

Also, we look why we should check all the types of a union type and make sure that both operands are of the same type before using the + operator.

Use startsWith and endsWith Instead of Other Equivalent Methods to Check Substrings

If we’re checking whether a string starts with or ends with some substring, then we should use the startsWith or endsWith methods to search for substrings.

For instance, instead of using:

foo.indexOf('bar') === 0;

or:

foo.substring(0, 3) === 'bar';

We can write:

foo.startsWith('bar');

Likewise, if we want if a string ends with something, instead of writing:

foo.slice(-3) === 'bar';

or:

foo.substring(foo.length - 3) === 'bar';

We can write:

foo.endsWith('bar');

It saves us lots of headaches with dealing with string indexes.

Use //@ts-expect-error over //@ts-ignore

//@ts-ignore suppresses all TypeScript errors before the erroring line.

If we use //@ts-expect-error , which is available since TypeScript 3.9, we can tell people that the error they encounter is expected instead of suppressing all errors.

Use any Function or a Method that Returns a Promise to be Marked async

The async-await syntax is a shorter way to write a function that returns a promise.

Therefore, we should use it as much as possible.

For instance, instead of writing:

const foo = () => Promise.resolve('foo');

We write:

const foo = async () => Promise.resolve('value');

Require array.sort Calls to Always Provide a Compare Function

By default, the sort method that’s part of an array instance sorts everything as if they’re strings.

Therefore, we should provide a comparator function so that we can compare our array entries properly.

For instance, if we want to sort numbers in ascending order, instead of writing:

const arr = [2, 3, 1];
arr.sort();

We write:

const arr = [2, 3, 1];
arr.sort((a, b) => a - b);

When Adding 2 Variables, Operands Must Both be of Type Number or String

The + operator is both used for addition or concatenation.

Therefore, we should make sure that we’re doing what we want by making both operands the same type.

Otherwise, we may get unexpected results.

For instance, instead of writing:

const foo = 1 + '1';

which will concatenate the operands, we should write:

const foo = 1 + Number('1');

so that they’re both numbers and we can add them together.

Use Template Expressions for Strings

Template strings are much better than regular strings. We can put expressions in them and we can make multiline expressions without hassle.

So we should use them as much as possible.

Therefore, instead of writing:

const arg = 'foo ' + bar;

We should write:

const arg = `foo ${bar}`;

Restrict Types Allowed in Boolean Expressions

We may want to restrict the types allowed in boolean expressions.

This may be useful if we don’t want automatic casting of truthy and falsy values.

For instance, instead of writing:

let foo = bar || 'baz';

We may write:

let foo = typeof bar !== 'undefined' ? bar : 'baz';

Make Sure that Union Type Checks are Exhaustive

We want to make sure that union type checks are exhaustive.

This way, we make sure that we don’t miss any cases in union types.

For instance, instead of writing:

type Color = "red" | "green" | "blue";

const color = "red" as Color;
let result = 0;

switch (color) {
  case "red": {
    result = 1;
    break;
  }
}

We write:

type Color = "red" | "green" | "blue";

const color = "red" as Color;
let result = 0;

switch (color) {
  case "red": {
    result = 1;
    break;
  }
  case "green": {
    result = 2;
    break;
  }
  case "blue": {
    result = 3;
    break;
  }
}

or:

type Color = "red" | "green" | "blue";

const color = "red" as Color;
let result = 0;

switch (color) {
  case "red": {
    result = 1;
    break;
  }
  default: {
    result = 100;
    break;
  }
}

Now we took all cases into account.

Conclusion

We should use startsWith and endsWith to check if a string starts with or ends with something.

Also, we may want to restrict the types allowed in boolean expressions to avoid casting.

And we want to make sure that we check all the types in a union type.

Finally, if we use the + operator, we should make sure that both operands are of the same type to prevent unexpected results.