Gallery
Department hub
Share
Explore
Code readability

icon picker
TypeScript

info
This is a sample page to show how subpages can connect to other pages. Clear out this sample info to flesh it out with your team’s resources, or turn it into something new!

Syntax and Structure

TypeScript

is a superset of JavaScript that provides static typing to the otherwise loosely-typed JavaScript. We use it because it provides the benefits of easy integration with the Node and React ecosystem while providing typing that ensures our code is well-structured and documented.

Coding guidelines

The TypeScript team publishes coding guidelines which are accessible
(ignore the STOP READING banner). We generally follow those, with two exceptions:
We use 2 spaces for indentation instead of 4
We prefix private/"package private" members with an underscore (_)

Editor

Now, on a little bit of a tangent. You can use any editor you like, of course, but some popular ones at Acme include:
: great integration with TypeScript, especially since it's developed by the same company (Microsoft) as TS itself. If you do use it, be sure to install the workspace-recommended extensions. You may also want to enable auto-formatting on save
: Tried and true JavaScript IDE with good static analysis and excellent visual git integration.
& : Doesn’t have much out of the box, but you can get full TypeScript integration, etc with configuration and plugins. The terminal nerds at #dev-linux can help you 😉

Line Width

We generally keep line width to a maximum of 120 characters.

Indentation

We use two-space indentation per level.

Declarations

Local variables should be declared as consts if they are not reassigned. Use one declaration per line, with constants first, followed by initialized lets and finally uninitialized lets.
const isFooAvailable = fooHelpers.isFooAvailable();
let count = 0;
let matchingBar;
Global variables & constants should be capitalized.
export const LookupTable = {};

Arrow Functions

In general, prefer arrow functions for inline functions, unless you need to maintain context (this, arguments, etc.) of the caller. However, do not use arrow functions as a replacement for class level functions. Example to follow:
this._foo = 1;
setTimeout(() => {
alert(this._foo + 2); // = 3
}, 1000);
Note that using these kinds of bound functions can result in the context being lost when you need it. For instance, passing a callback that makes use of this to some other component would cause it to not work properly by default; you'll have to bind the function first before passing it like so -
grid.events.on('rowDeleted', this._dispose); // Fails as `this` used inside _dispose() may be bound to something else.
grid.events.on('rowDeleted', rowId => this._dispose(rowId)); // `this` context will work correctly here.
Note too that arrow functions are always recreated on every React rerender. This may lead to some unintended consequences. For instance, using arrow functions for setting React refs sometimes lead to race conditions where a component is mounted but its ref is null. Whenever possible, use bound methods on the class. So instead of:
return (
<MyComponent
ref={r => { this._myComponentRef = r; }}
/>
);
use:
return (
<MyComponent
ref={this._setMyComponentRef}
/>
);
See "Caveats" in for more info on this.

Destructure props as part the function definition

function MyComponent({x, y}: Props) {
// ...
}

Method Signatures

Required parameters to methods should be specified directly, and methods should in general not have more than 4-5 parameters. Beyond that, consider refactoring to group related arguments into strongly-typed data objects. When a function uses default parameters, especially more than one, consider placing them into an optional object following required parameters, e.g.:
interface Params {
bar?: number;
baz?: boolean;
useQux?: boolean;
}
function computeFoo(start: number, end: number, {bar = 123, baz, useQux = true}: Params = {}) {
/* ... */
}

String Interpolation

Use string interpolation syntax to concatenate or form strings:
const message = `Hello, ${name}. Your monthly fee is \$${computeMonthlyFee(email)}.`;

Rest and Spread

Although seldom needed, these operators can come in useful in some cases:
const sum = (a: number, ...rest: number[]) => a + (rest.length ? sum(...rest) : 0);
sum(1, 2, 3); // => 6
Try to avoid using the spread operator on large arrays.

Object Properties

We make use of ES6/TypeScript syntax such as object and array destructuring, property shorthands, and computed keys.
const {invalid, rawValue: value} = this.props; // Equivalent to writing: value = this.props.rawValue;
const [first, last = DEFAULT_LAST] = this._computePairs();

return (
<MyComponent
className={classNames(classes.root, {[classes.invalid]: invalid})}
first={first}
foo={123}
last={last}
value={value}
/>
);

Generators

While seldom used, you may find the use of in our codebase.

Importing and Exporting

Importing

Import statements should be together in one block and consumers of those statements should be in a separate block. We have a Lint rule that will auto fix up imports (easiest to use if you have format-on-save enabled).
import {BaseComponent} from '@kr-browser/react-components/base_component';
import React from 'react';
import {SomeConstant} from '../../constants';
import {TooltipView} from '@kr-browser/react-components/tooltip_view';
import {Value} * '@kr-common/constants';
import classNames from 'classnames';
import * as domHelpers from '../../helpers/dom';

const {DOM: dom} = React;

Exporting

Most TypeScript files we work with are classes or constants. These should be exported as needed with the export keyword. index.ts files within each package should then re-export public objects/classes for use across other packages.
export class MyClass {
// ...
}

export SomeConstant = 'SOME_CONSTANT;

Lodash

Our utility library of choice is , but we’ve been moving away from it given ES6 and related functionality typically performs better. We usually prefer native implementations over lodash unless there’s a specific performance reason to do otherwise.

Property and matching shorthand notations

Lodash functions make use of several shorthand notations for working with data structures:
const data = [
{value: 1, foo: 'a'},
{value: 2, foo: 'b'},
{value: 3, foo: 'c'},
];
console.log(_.map(data, 'value')); // Outputs: [1, 2, 3]
console.log(_.filter(data, {value: 2})); // Outputs: [{value: 2, foo: 'b'}]

Caveats

As with any library, keep in mind performance and readability. There are some functions in Lodash, such as _.rearg, that are usually better left alone. That is, sometimes a for of loop might be clearer than a complex sequence of seldom-used Lodash functions. Furthermore, avoid using something like _.size(arr) or _.first(arr) when arr.length or arr[0] would suffice: Lodash performs many runtime checks, which can be slow in hot code paths.

Promises

let us perform asynchronous computations, and we make heavy use of them in our backend (and often our frontend) code.

async/await

You should use , which makes asynchronous code read as easily as synchronous code.

Example Usage

Check out of how we use promises. AccountsManager#createUser() starts by verifying whether someone is allowed to create an account. If so, it asks the accounts storage Postgres implementation to create a new user in the users table, and proceeds to save the Google authentication token in a different table. If there is a failure along the way, the catch() block logs a warning and throws an unauthorized error.

Take care with returning values within try/catch!

Consider the following function:
async function processResult(data) {
try {
return someOtherAsyncFunction(data);
} catch (err) {
if (shouldHandleError(err)) {
return;
}
throw err;
}
}
It may be expected that errors thrown (or rather promise rejections from) someOtherAsyncFunction would flow into the catch block. This is not the case. Instead, the promise for someOtherAsyncFunction is immediately returned. To have the code flow to the catch block, you need to await the promise, like:
async function processResult(data) {
try {
return await someOtherAsyncFunction(data);
} catch (err) {
if (shouldHandleError(err)) {
return;
}
throw err;
}
}

Throwing Errors

Take care not to mix synchronous code with promisified code. For instance, consider this function:
function processResult(data) {
if (!_.isArray(data) || data.length < 2) {
throw new Error('Expected at least two values in result');
}

return promise.resolve(_.map(data, value => dataHelpers.processValue(value)));
}
processResult() returns a promise in the success case, but throws a synchronous error in the other cases, which is bad. Instead, simply mark the function as async which causes a rejected promise to be returned in either case.
async function processResult(data) {
if (!_.isArray(data) || data.length < 2) {
throw new Error('Expected at least two values in result');
}

return data.map(value => dataHelpers.processValue(value));
}
If you absolutely need to mix promises with sync values, make sure to use the MaybePromise return type and not to default to an async function, which can create additional overhead if the value is not a promise. Check out the handleMaybePromise helper in the promise section of js-core for how to work with MaybePromise values.

Share
 
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.