Coding Conventions
May 4, 2017 ยท View on GitHub
Here you can read about our preferred ways of writing modern JavaScript.
Classes
Use ES2015 classes instead of prototype-based inheritance. Having a single declarative form makes class patterns easier to use, and encourages interoperability.
class Todo {
constructor(text) {
this.text = text
}
}
Class properties
Use instance properties to define initial values for class properties instead of defining them inside the constructor. Constructors are more useful when the class takes its initial values from the outside.
Note: You need to include the transform-class-properties Babel plugin in order to use class properties.
Do
class Todo {
text = 'Something to do'
}
class Todo {
constructor(text) {
this.text = text
}
}
Do NOT do
class Todo {
constructor() {
this.text = 'Something to do'
}
}
Static keyword
Use Static keyword
instead of the traditional Todo.staticMethod syntax because it will allow you to contain all code related to particular class inside the class itself.
Do
class Todo {
static label = 'Todo'
static staticMethod() {
console.log('Static method has been called')
}
static anotherStaticMethod() {
Todo.staticMethod()
}
}
Do NOT do
class Todo {}
Todo.label = 'Todo'
Todo.staticMethod = () => {
console.log('Static method has been called')
}
Todo.anotherStaticMethod = () => {
Todo.staticMethod()
}
Arrow functions
Use arrow functions instead of traditional functions because they share the same lexical this (or scope) with the surrounding code, which is desirable in most cases.
Class methods
Arrow functions are especially useful as class methods, because you do not need to bind every method to the scope separately.
Do
class Todos {
addTodo = (todo) => {
// ...
}
}
Do NOT do
class Todos {
constructor() {
this.addTodo = this.addTodo.bind(this);
}
addTodo(todo) {
// ...
}
}
Function expressions
Arrow functions (or function expressions) are more convenient than traditional functions, especially when currying, because you do not need to add a return statement for each nested function and you can even omit the curly brackets for one-line functions.
Note: Functions that are defined using the function keyword (also known as named functions) are hoisted to the top of the scope, which means that you can invoke them even before they are defined. This can promote bad design and cause confusion.
Do
const addTodo = () => {
// ...
}
Do NOT do
function addTodo() {
// ...
}
Resources
Async functions
Use async/await instead of Promise.prototype.then.
Do
try {
const response = await fetch('https://example.com')
const body = await response.json()
console.log(body)
} catch (error) {
console.error(error)
}
Do NOT do
fetch('https://example.com')
.then((response) => response.json())
.then((body) => {
console.log(body)
})
.catch((error) => {
console.error(error)
})
Use Promise.all when running multiple async functions in parallel.
Do
const [orders, customers] = await Promise.all([
getOrders(),
getCustomers(),
])
console.log(orders, customers)
Do NOT do
Promise.all([
getOrders(),
getCustomers(),
]).then(([orders, customers]) => {
console.log(orders, customers)
})
Template literals
Use template literals instead of string concatenation. Template literals allow for embedded expressions and you can use them even with multi-line strings.
Do
const hello = `Hello ${name}!
This syntax is so convenient.`
Do NOT do
const hello = 'Hello ' + name + '!' +
'This syntax is not so convenient.'
Destructing assignment
Use destructing assignment wherever possible. Destructing allows for a shorter syntax that is easier to read.
Do
const Todos = ({todos, onTodoClick}) =>
<ul>
{todos.map((todo) =>
<li><a onClick={() => onTodoClick(todo)}>{todo.text}</a></li>
)}
</ul>
Do NOT do
const Todos = (props) =>
<ul>
{props.todos.map((todo) =>
<li><a onClick={() => props.onTodoClick(todo)}>{todo.text}</a></li>
)}
</ul>
Spread syntax
Use spread syntax instead of Function.prototype.apply.
Do
const myFunction = (x, y, z) => { /* ... */ }
const args = [0, 1, 2]
myFunction(...args)
Do NOT do
const myFunction = (x, y, z) => { /* ... */ }
const args = [0, 1, 2]
myFunction.apply(null, args)
The spread syntax is also a better way to both copy and concatenate arrays and objects.
Do
const arr = [0, 1, 2]
const arr2 = [...arr]
arr2.push(3)
const obj = {x: 1}
const obj2 = {...object, y: 2}
const obj2.z = 3
Do NOT do
const arr = [0, 1, 2]
const arr2 = arr.slice()
arr2.push(3)
const obj = {x: 1}
const obj2 = Object.assign({y: 2}, obj)
const obj2.z = 2
Variables
Const
Use const whenever possible. Note that you can use const even for arrays and objects as long as you do not assign a new reference to that variable.
Do
const arr = [0, 1, 2]
arr.push(3)
Do NOT do
let arr = [0, 1, 2]
arr.push(3)
Let
Use let when you need a non-constant local variable.
Do
for (let i = 0; i < arr.length; i++) {
// ...
}
Do NOT do
for (var i = 0; i < arr.length; i++) {
// ...
}
Note: The traditional var should be avoided at all cost.
Functional style
Write functional JavaScript when possible, but keep in mind that some times you are better off with object-oriented JavaScript. Avoid writing imperative JavaScript at all cost.
Do
const capitalize = (str) =>
[str.charAt(0).toUpperCase(), str.substr(1)].join('')
const processWords = (fn, str) =>
str.split(' ').map(fn).join(' ')
console.log(processWords(capitalize, 'The quick brown fox jumps over the lazy dog'))
Do NOT do
const capitalizeWords = (str) => {
let result = []
const arr = str.split(' ')
for (let i = 0; i < arr.length; i++) {
result.push(arr[i].charAt(0).toUpperCase() + arr[i].substring(1))
}
return result.join(' ')
}
console.log(capitalizeWords('The quick brown fox jumps over the lazy dog'))
Modules
Use ES6 modules instead of CommonJS or AMD modules because it is the standard way of defining modules.
Do
export default () => 'Hello from an ES6 module'
Do NOT do
module.exports = () => 'Hello from an old module'