Writing modern SPA applications
part 1: tooling and environment
ES6 - what's new?
let
(function() {
var foo = 'A'
if(foo !== false) {
var foo;
console.log(foo) // this will print "A" to console
}
})()
(function() {
let foo = 'A'
if(foo) {
let foo;
console.log(foo) // this will print "undefined" to console
}
})()
const
const foo = {};
foo = 4 // this will crash
foo.a = 4 // this will not
FAT ARROW SYNTAX
function Something() {
setTimeout(() => console.log(this), 1) // will log correct instance Something() {}
setTimeout(function() { console.log(this) }, 1) // will log Window
}
const instance = new Something()
const foo = () => { 123 } // <- this works like normal function
// and will return `undefined`
const bar = () => ( 123 ) // <- this works like expression and will return `123`
const bar = () => 123 // <- notice that () are optional in some cases,
// for example arithmetric operations or simple values
PROMISES
- Promises (or Futures, in other languages) are incomplete value objects.
- They can be in either of three states: pending, resolved or rejected.
- When the asynchronous operation ends it can either resolve or reject a Promise, passing a value (or error) to it.
- Promises are one of the simplest synchronisation constructs.
PROMISES
// using callbacks
getUser(1, {
success: (user) => {
this.user = user;
},
failure: (error) => {
this.error = error;
}
);
// using promises
getUser(1)
.then((user) => {
this.user = user
})
.catch(error) => {
this.error = error;
})
PROMISES CAN BE
CHAINED
PROMISE CHAINING
// using callbacks
doSomething('1', function(resultA) {
thenDoSomething(resultA, function(resultB) {
andSomethingElse(resultB, function(resultC) {
// WE NEED TO GO DEEPER
})
})
})
// using promise chaining
doSomething('1')
.then(thenDoSomething)
.then(andSomethingElse)
.then((result) => {
// WE NEED TO GO DEEPER
})
PROMISES CAN BE
SYNCHRONISED
PROMISE.ALL
const getUsers = fetch('/users')
const getProjects = fetch('/projects')
Promise.all([ getUsers, getProjects ]).then((results) => {
// this can be simplified with destructuring, we'll get there
const users = results[0]
const projects = results[1]
})
PROMISES CAN BE
WRITTEN LIKE SYNCHRONOUS CODE
(wait, what?)
ASYNC/AWAIT SYNTAX
async function getResult() {
const resultA = await doSomething('1')
const resultB = await thenDoSomething(resultA)
const resultC = await andSomethingElse(resultB)
// WE NEED TO GO DEEPER
}
try {
const response = await axios('/users')
console.log(response.data)
} catch(error) {
if(error.response && error.response.code === 404) {
console.log("Not found")
}
}
DESTRUCTURING
- Simplifies working with hashes (objects)
- Work both with {} objects and [] arrays
- You can pass default parameters
DESTRUCTURING - simple case
const object = { foo: 1, bar: 2}
const { foo, bar } = object
// this is equivalent of
const foo = object.foo
const bar = object.bar
DESTRUCTURING - NESTED
const object = { a: { b: 1 } }
const { a: { b, c }} = object
// this is equivalent of
const b = object.a.b
const c = object.a.c
// for most cases this is more readable
const { b, c } = object.a
DESTRUCTURING - ARRAYS
const list = [1, 2]
const [ foo, bar ] = list
// this is equivalent of
const foo = list[0]
const bar = list[1]
DESTRUCTURING - DEFAULTS
const emptyObject = {}
const { foo = 1, bar } = emptyObject
// this is (more or less) equivalent of
const foo = typeof emptyObject.foo !== 'undefined' ? emptyObject.foo : 1
const bar = emptyObject.bar
DESTRUCTURING - DIFFERENT NAMES
const emptyObject = { first: 1, last: 2 }
const { first: firstNumber, last: lastNumber } = emptyObject
console.log(firstName, lastName) // will log 1 2
console.log(first, last) // this will throw TypeError because first and last
// are not defined
PROPERTY SHORTHAND AND COMPUTED PROPERTY NAMES
const name = "Me"
const date = Date.now
// explicitly stating key is unnecessary if it's the same as variable name
const user = { name, date }
const key = "Key"
const anotherKey = "anotherKey"
// keys can be dynamically calculated from variables now
const myObject = {
[key]: 'Value',
[anotherKey]: 'anotherValue'
}
es6 classes
- Classes are just syntactic sugar over already existing prototype-based functions
- They can inherit from one another...
- ...but inheriting from standard library is still not the best idea
- They can also have properties with getters and setters
classes - constructor
class Model {
constructor(attributes) {
this.attributes = attributes
}
}
const me = new Model({ name: 'Michal' })
console.log(me.attributes) // logs { name: 'Michal' }
classes - INHERITANCE
class Model {
constructor(attributes) {
this.attributes = attributes;
}
}
class User extends Model {
constructor(attributes) {
super(attributes);
}
}
const me = new User({ name: 'Michal' })
console.log(me.attributes) // logs { name: 'Michal' }
classes - GETTERS AND SETTERS
class User extends Model {
constructor(attributes) {
this.attributes = attributes;
}
get name() {
return this.attributes.name
}
set name(value) {
return this.attributes.name = value
}
}
const me = new User({ name: 'Michal' })
console.log(me.name) // logs 'Michal'
me.name = 'Maciek'
console.log(me.name) // logs 'Maciek'
IMPORT/export syntax
- Official standard for Javascript modules (finally!)
- Works out of the box with Babel and/or Webpack
- They allow multiple named exports from one file (and one default export)
IMPORT/export syntax
export function createUser() {}
const updateUser = () => {}
export { updateUser }
export default MyClass
import React from 'react'
import { FormGroup } from 'react-bootstrap'
import each from 'lodash/each'
import { createUser }, MyClass from './export_example'
decorators
- Wrappers around class or class method.
-
Can be used as cleaner alternative for wrapping class methods in constructor.
- Heavily used by MobX (though not required).
decorators
- Not really a standard just yet, the specification was heavily changed since they were adopted.
-
Original proposal is now called "legacy" and require Babel transform plugin (transform-decorators-legacy).
- Current standard (stage-2) is not yet implemented anywhere.
- This is not a problem as we can treat legacy (stage-0) decorators as any other syntactic sugar applied via Babel.
DECORATORS
import { debounce } from 'lodash-decorators'
class Updater {
constructor(selector) {
document.querySelectorAll(selector).addEventListener('change', this.onChange)
// without decorators we would write this:
// this.onChange = _.debounce(this.onChange, 200)
}
@debounce(500)
onChange(e) {
axios.post('/updates', { [e.target.name]: e.target.value })
}
}
const updater = new Updater('form input[type="text"]')
right, but what about
legacy browsers
BABEL.js
(formerly 6to5)
babel.js
- Javascript transpiler.
- Converts code from one syntax to another using a set of plugins / transformers.
- Allows heavy configurability by turning on single transformers and broad configuration with presets.
- Can be used as standalone tool using babel-cli npm package.
babel.js presets
- Sets of predefined or dynamically defined plugins, you don't need to use them but they are wildly useful
- You can use es2015/es2016 directly or use env preset which automatically updates based on browser vendors progress
- stage-0, stage-1 etc. presets add plugins for experimental or unstable features of the language that are still in works and not a fully accepted spec (like decorators)
- There's also preset "react" for JSX
WEBPACK
WEBPACK
- Module bundler, used to create javascript "bundles" but also to prepare and handle other assets like images, fonts, CSS etc
- Uses loaders to transform assets in similar way to babel (integration with babel is done with babel-loader)
- Uses plugins to apply changes after transformations (for ex. minification or string replacement) or to create extra files (for ex. HtmlWebpackPlugin)
- It's used both for compilation in development (with live reloading and watching) and production
wait, this is confusing
what's the difference?
BABEL
- Handles Javascript transformation
- Cares only about Javascript
- Does not resolve import/export (they are just part of language so they end up unchanged in compiled code*)
* unless you use a preset or plugin that transforms them to something else
WEBPACK
- Transforms files into different files using loaders
- Supports non-javascript files, can pipe them through loaders and plugins
- Creates dependency tree from Javascript files (compiled with babel-loader or not) and other assets
- Resolves import/export syntax
LET'S PAUSE FOR NOW
IT'S Q/A TIME!
Writing modern SPA applications
By Michał Matyas
Writing modern SPA applications
- 764