Typescript Style Guide
WWNorton TypeScript Style Guide
Official Typescript Documentationโ
Familiar With TypeScript already?
The starting point for learning TypeScript
TypeScript Best Practicesโ-โSemicolons and Spacing
Generalโ
- No Unused Expressions
We shouldn't have unused expressions in our code. If we have them, we should remove them.
// Bad ๐
a + b;
// Good ๐
let add = a + b;
- Filename
Name files with camelCase
.
Reason: Conventional across many JS teams.
accordion.tsx
myControl.tsx
stringUtils.ts
map.ts
Variables and Functionโ
- Use
camelCase
for variable and function names
Reason: Conventional JavaScript
// Bad ๐
var FooVar;
function BarFunc() { }
// Good ๐
var fooVar;
function barFunc() { }
- No Unused Variables
@typescript-eslint/no-unused-vars
Likewise, if we have unused variables, then we should remove them.
// Bad ๐
var num1 = 10;
var num2 = 7;
var result = num1 + 7;
// Good ๐
var num1 = 10;
var result = num1 + 7;
// Good ๐
var num1 = 10;
var num2 = 7;
var result = num1 + num2;
- 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.
// Bad ๐
var hoisted;
console.log(hoisted); // will be undefined;
// Good ๐
let notHoisted;
const alsoNotHoisted;
console.log(notHoisted); // Will throw an error (which is what we want in this case);
console.log(alsoNotHoisted); // Will also throw an error (which is what we want in this case);
- Replace Magic Numbers with Named Constants
@typescript-eslint/prefer-as-const
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:
// Bad ๐
enum Foo { bar = 1 }
// Good ๐
const NUMBER_OF_BARS = 1;
enum Foo { bar = NUMBER_OF_BARS }
- Donโt Use async if await isnโt Used Inside the Function
@typescript-eslint/require-await
We should useย
async
ย functions only if we have toยawait
ย something inside it.
For instance, if we have something like:
// Bad ๐
const foo = async () => "bar";
// Good ๐
const foo = async () => {
const url = "some.domain.com";
let response = await fetch(url, {
method: 'GET'
});
}
then we should use a normal function.
- Return Awaited Values Consistently
We should not 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 to this is that we can putย
return
ย andยawait
ย on the same line inside aยtry
ย block to catch errors from another promised-based function
// Bad ๐
async function foo() {
return await bar();
}
// Good ๐
async function foo() {
const val = await bar();
return val;
}
// Good ๐
async function foo() {
try {
return await bar();
} catch (error) {}
}
Arrayโ
- Annotate arrays as
foos:Foo[]
instead offoos:Array<Foo>
.
Reasons: Its easier to read. Its used by the TypeScript team. Makes easier to know something is an array as the mind is trained to detect [].
// Bad ๐
foos:Array<Foo>;
// Good ๐
foos:Foo[];
Stringsโ
- Prefer single quotes (
'
) unless escaping.
Reason: More JavaScript teams do this (e.g. airbnb, standard, npm, node, google/angular, facebook/react). Its easier to type (no shift needed on most keyboards). Prettier team recommends single quotes as well
Double quotes are not without merit: Allows easier copy paste of objects into JSON. Allows people to use other languages to work without changing their quote character. Allows you to use apostrophes e.g. He's not going.. But I'd rather not deviate from where the JS Community is fairly decided.
// bad ๐
var foo = "bad";
// good ๐
var foo = 'bar';
- When you can't use double quotes, try using back ticks (`).
@typescript-eslint/restrict-template-expressions
Reason: These generally represent the intent of complex enough strings.
// good ๐
var foo = `this foo has a dynamic ${bar}`;
- 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.
Classโ
- Use
PascalCase
for class names.
Reason: This is actually fairly conventional in standard JavaScript.
// Bad ๐
class fooBoo { }
// Good ๐
class FooBoo { }
- Use
camelCase
of class members and methods
Reason: Naturally follows from variable and function naming convention.
// Bad ๐
class Foo {
BarVar: number;
BazFunc() { }
}
// Good ๐
class Foo {
barVar: number;
bazFunc() { }
}
- No Useless Constructors
@typescript-eslint/no-useless-constructor
We shouldnโt have useless constructors.
// Bad ๐
class A {
constructor () {
}
}
// Bad ๐
class B extends A {
constructor (value) {
super(value);
}
}
Theyโre both redundant so they should be removed.
Interfaceโ
- Use
PascalCase
for name.
Reason: Similar to class
// Bad ๐
interface themeProps {
color: string;
}
// Good ๐
interface ThemeProps {
color: string;
}
- Use
camelCase
for members.
Reason: Similar to class
// Bad ๐
interface ColorProps {
FirstColor: string;
second-color: string;
}
// Good ๐
interface ColorProps {
firstColor: string;
secondColor: string;
}
- Don't prefix with
I
Reason: Unconventional. lib.d.ts defines important interfaces without an I (e.g. Window, Document etc).
// Bad ๐
interface IFoo {
}
// Good ๐
interface Foo {
}
Typeโ
- Use
PascalCase
for name.
Reason: Similar to class
// Bad ๐
type infoBaseProps = 'htmlFor' | 'className' | 'children' | 'id';
// Good ๐
type InfoBaseProps = 'htmlFor' | 'className' | 'children' | 'id';
- Use
camelCase
for members.
Reason: Similar to class
// Bad ๐
type Animal = {
animalrace: string
}
// Good ๐
type Animal = {
animalRace: string
}
Type vs. Interfaceโ
- Use
type
when you might need a union or intersection:
// Good ๐
type Foo = number | { someProperty: number }
- Use
interface
when you wantextends
orimplements
e.g
// Good ๐
interface Foo {
foo: string;
}
interface FooBar extends Foo {
bar: string;
}
class X implements FooBar {
foo: string;
bar: string;
}
Namespaceโ
- Use
PascalCase
for names
Reason: Convention followed by the TypeScript team. Namespaces are effectively just a class with static members. Class names are PascalCase => Namespace names are PascalCase
// Bad ๐
namespace fooBaz {
}
// Good ๐
namespace FooBaz {
}
Enum
- Use
PascalCase
for enum names
Reason: Similar to Class. Is a Type.
// Bad ๐
enum colorOne {
}
// Good ๐
enum ColorOne {
}
- Use
PascalCase
for enum member
Reason: Convention followed by TypeScript team i.e. the language creators e.g SyntaxKind.StringLiteral. Also helps with translation (code generation) of other languages into TypeScript
// Bad ๐
enum Color {
redHue
}
// Good ๐
enum Color {
RedHue
}
Null vs. Undefinedโ
- Prefer not to use either for explicit unavailability
Reason: these values are commonly used to keep a consistent structure between values. In TypeScript you use types to denote the structure
// Bad ๐
let foo = { x:123, y:undefined };
// Good ๐
let foo:{ x:number, y?:number } = { x:123 };
- Use
undefined
in general (do consider returning an object like{valid:boolean,value?:Foo}
instead)
// Bad ๐
return null;
// Good ๐
return undefined;
- Use
null
where its a part of the API or conventional
Reason: It is conventional in Node.js e.g. error is null for NodeBack style callbacks.
// Bad ๐
cb(undefined)
// Good ๐
cb(null)
- Use truthy check for objects being
null
orundefined
// Bad ๐
if (error === null)
// Good ๐
if (error)
- Use
== null
/!= null
(not===
/!==
) to check fornull
/undefined
on primitives as it works for bothnull
/undefined
but not other falsy values (like''
,0
,false
) e.g.
// Bad ๐
if (error !== null) // does not rule out undefined
// Good ๐
if (error != null) // rules out both null and undefined
Tabs vs. Spacesโ
- Use tabs over spaces.
Reason: Use tabs for indenting your code. With tabs users can choose their desired width. This has positive implications for accessibility and screenreaders, putting people in control of how they want to view the code.
// bad ๐
function foo() {
โโlet name;
}
// bad ๐
function bar() {
โlet name;
}
// good ๐
function baz() {
โ let name;
}
Semicolonsโ
- Use semicolons.
Reasons: The usage of semicolons is a core value of our community. Consider the points of the opposition, but be a traditionalist when it comes to abusing error correction mechanisms for cheap syntactic pleasures. Explicit semicolons helps language formatting tools give consistent results. Missing ASI (automatic semicolon insertion) can trip new devs.
// Bad ๐
let x = 5
// Good ๐
let x = 5;
We should put in semicolons in our code instead of letting the JavaScript interpreter add them for us in unexpected places.
- Remove Useless Semicolons
@typescript-eslint/no-extra-semi
We should remove duplicate semicolons in our code. We only need one semicolon at the end of a statement.
// Bad ๐
let x = 5;;
// Good ๐
let x = 5;
- Require Semicolons Instead of Automatic Semicolon Insertion
Instead of letting the Javascript interpreter put in semicolons for us, we should put them in ourselves.
// Bad ๐
function returnObject() {
return //<-----semi colon will be inserted causing an error.
{
key: value
};
}
// Good ๐
function returnObject() {
return {
key: value
};
}
Import / Exportโ
- Avoid export default
Avoid
export default
in TypeScript because of the maintainability and DX issues outlined in Typescript Deep Dive - Avoid Export Default.
Example:
// foo.ts
class Foo {
}
export default Foo;
// bar.ts
import Foo from "./foo";
- If you refactor
Foo
infoo.ts
it will not rename it inbar.ts
. - If you end up needing to export more stuff from
foo.ts
(which is what many of your files will have) then you have to juggle the import syntax
// Bad ๐
// foo.ts
export default class Foo {
return
{
key: value
};
}
// bar.ts
import Foo from './foo';
// Good ๐
// foo.ts
export class Foo {
return {
key: value
};
}
// bar.ts
import { Foo } from "./foo";