Key Sections:
- Case (
camelCase
vs.PascalCase
vs.ALL_CAPS
) - Interface names
- Test filenames
- Type files
null
vs.undefined
const
vs.let
vs.var
- Formatting
- Quotes (single vs. double)
- Use semicolons
- Arrays (annotate as
Type[]
) type
vs.interface
- One-line
if
statements import
sKeyboardEvent
s- Components
- Function return type
- Hooks
- Use
camelCase
for variable and function names
Reason: Conventional JavaScript
Bad
var FooVar;
function BarFunc() {}
Good
var fooVar;
function barFunc() {}
- Use
PascalCase
for component name, even if function component.
Reason: Follows React naming convention.
Bad
function component(): ReactElement {
return <subComponent />;
}
Good
function Component(): ReactElement {
return <SubComponent />;
}
- Use
PascalCase
for class names.
Reason: This is actually fairly conventional in standard JavaScript.
Bad
class foo {}
Good
class Foo {}
- Use
camelCase
of class members and methods
Reason: Naturally follows from variable and function naming convention.
Bad
class Foo {
Bar: number;
Baz() {}
}
Good
class Foo {
bar: number;
baz() {}
}
- Use
PascalCase
for name.
Reason: Similar to class
- Use
camelCase
for members.
Reason: Similar to class
- Use
PascalCase
for name.
Reason: Similar to class
- Use
camelCase
for members.
Reason: Similar to class
- Use
ALL_CAPS
for action types.
Reason: Follows Redux naming convention
- 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 arePascalCase
Bad
namespace foo {}
Good
namespace Foo {}
- Use
PascalCase
for enum names
Reason: Similar to Class. Is a Type.
Bad
enum color {}
Good
enum Color {}
- 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 {
red,
}
Good
enum Color {
Red,
}
Name files and folders with camelCase
(e.g., utilities.ts
, index.tsx
, tractor.png
, components/
), unless it is
the name of the contained Component (e.g., DataEntryTable.tsx
, ReviewEntries/
).
Reason:
camelCase
is conventional across many JS teams.
- Don't prefix with
I
Reason: Unconventional.
lib.d.ts
defines important interfaces without anI
(e.g. Window, Document etc).
Bad
interface IFoo {}
Good
interface Foo {}
-
The test file associated with
path/to/Component.tsx
should bepath/to/tests/Component.test.tsx
. -
Auxiliary test files with data or mock objects should have filenames ending in
Mock.ts
, e.g.,path/to/tests/DataMock.ts
.
Reason:
*.test.*
and*Mock.ts
files are ignored by our CodeCov settings.
- Separate type files should contain
type
s,class
es,interface
s,enum
s but not any classes or functions that need unit testings. Such files should have filenames ending inTypes.ts
, e.g.,MergeDupReduxTypes.ts
.
Reason:
*Types.ts
files are ignored by our CodeCov settings.
- 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
isnull
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)
Remark: Use
===
/!==
to check fornull
/undefined
on primitives that might be other falsy values (like''
,0
,false
).
-
const
allows the variable to be mutated, but doesn't allow it to be redeclared; preferconst
any time your variable only needs to be declared once. -
let
andvar
both allow redeclaration, butvar
is a global variable andlet
is limited to the scope of its declaration; preferlet
tovar
.
Bad
var shouldClapHands = false;
for (var i = 0; i < 3; i++) {
var you = getYouAtTime(i);
shouldClapHands ||= you.isHappy() && you.knowsIt();
}
Good
let shouldClapHands = false;
for (let i = 0; i < 3; i++) {
const you = getYouAtTime(i);
shouldClapHands ||= you.isHappy() && you.knowsIt();
}
Use Prettier to format TypeScript code as described in the README.
Reason: Reduce the cognitive overload on the team.
- Prefer double quotes (
"your text"
) to single quotes ('your text'
).
Reason: Follows Prettier naming convention.
- Use semicolons.
Reasons: Explicit semicolons helps language formatting tools give consistent results. Missing ASI (automatic semicolon insertion) can trip new devs e.g.
foo() \n (function(){})
will be a single statement (not two). TC39 warning on this as well. Example teams: airbnb, idiomatic, google/angular, facebook/react, Microsoft/TypeScript.
- 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
[]
.
- Use
type
when you might need a union or intersection:
type Foo = number | { someProperty: number }
- Use
interface
when you wantextends
orimplements
e.g
interface Foo {
foo: string;
}
interface FooBar extends Foo {
bar: string;
}
class X implements FooBar {
foo: string;
bar: string;
}
- Otherwise use whatever makes you happy that day.
- Add braces to one-line
if
statements;
Good
if (isEmpty) {
callFun();
}
Bad
if (isEmpty)
callFun();
Reason: Avoiding braces can cause developers to miss bugs, such as Apple's infamous goto-fail bug
- Use absolute
import
statements everywhere for consistency.
Good
import { type Project } from "api/models";
import { getAllProjects } from "backend";
Bad
import { type Project } from "../../../../api/models";
import { getAllProjects } from "../../../../backend";
Reason: Provides consistency for imports across all files and shortens imports of commonly used top level modules. Developers don't have to count
../
to know where a module is, they can simply start from the root ofsrc/
.
- Generally import the specific things needed (e.g., not
React
when{ type ReactElement }
will do), and from a more specific target (e.g.,from "api/models"
rather thanfrom "api"
):
Good
import { type ReactElement } from "react";
import { type Project } from "api/models";
function Component(props: { project: Project }): ReactElement {}
Bad
import React from "react";
import { type Project } from "api";
function Component(props: { project: Project }): React.ReactElement {}
- Use
ts-key-enum
when comparing toReact.KeyboardEvent
s.
Good
import { Key } from "ts-key-enum";
if (event.key === Key.Enter) {
}
Bad
if (event.key === "Enter") {
}
Reason: Avoid typos and increase the number of mistakes that can be caught at compile time.
- Prefer functional components to class components.
Good
function Component(props: ComponentProps): ReactElement {
return <SubComponent />;
}
Bad
class Component extends React.Component<ComponentProps, ComponentState> {
render() {
return <SubComponent />;
}
}
- Specify the return type of a named function, whether declared as a
function
orconst
. - The return type of a functional component should be
ReactElement
.
Good
import {ReactElement} from React;
function Component(props: { name: string }): ReactElement {
const sayHi = (name: string): string => {
return `Hi, ${name}!`;
}
return sayHi(props.name);
}
Bad
function Component(props: { name: string }) {
const sayHi = (name: string) => {
return `Hi, ${name}!`;
};
return sayHi(props.name);
}
Reason: Ensure all return paths are the expected type.
- Prefer
useAppDispatch
touseDispatch
anduseAppSelector
touseSelector
.
Good
import { useAppDispatch, useAppSelector } from "rootRedux/hooks";
import { type StoreState } from "rootRedux/types";
function Component(): ReactElement {
const dispatch = useAppDispatch();
const subState = useAppSelector((state: StoreState) => state.subState);
}
Bad
import { useDispatch, useSelector } from "react-redux";
function Component(): ReactElement {
const dispatch = useDispatch();
const subState = useSelector((state) => state.subState);
}
Reason: See
types/hooks.ts
.