Deep-dive dans ES6

Une présentation de Christophe Porteneuve à Blend Web Mix 2015

Tékitoa


var christophe = {
  age:        37.98015058179329,
  city:       'Paris',
  company:    'Delicious Insights',
  trainings:  ['JS Total', 'Node.js', 'Git Total'],
  webSince:   1995,
  claimsToFame: [
    'Prototype.js',
    'Ruby On Rails',
    'Bien Développer pour le Web 2.0',
    'Prototype and Script.aculo.us',
    'Paris Web'
  ]
};
          

Déjà ?!

Je fais même pas encore de l’ES5, moi…

Oui, mais c’est pas grave

Parce que

Logo de Babel

Babel (né 6to5)

Transpile ES6+1 (ES6, ES7…) en ES5

Du coup, si tu as ES5, tu peux y aller

En pratique : IE9+, tous navigateurs modernes, Node.js

Intégré avec l’univers

1 Quasi tout… mais pas complètement tout.

ES6, pourquoi ?

Plus facile

Plus puissant

Plus fiable

Plus performant

Plus kiffant

Aujourd’hui…

on va regarder ES6 dans son habitat naturel

Parce que juste sortir des exemples Toto / Tutu, ça empêche de vraiment intérioriser le potentiel du bestiau.

Véritable codebase : l’exemple TodoMVC de Redux

Quelques démos isolées lorsqu’on n’avait pas d’exemple adapté dans cette codebase.

On ne verra pas tout : certains trucs sont anecdotiques ou très, très expérimentaux. Mais ça devrait bien couvrir le truc.

Objets & Classes

Là c’est grave plus simple

Litéraux objets


function editTodo(id, text) {
  return { type: types.EDIT_TODO, id, text };
}
            

FILTER_TITLES = {
  [SHOW_ALL]: 'All',
  [SHOW_ACTIVE]: 'Active',
  [SHOW_COMPLETED]: 'Completed'
};
            

Classes


class TodoItem extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      editing: false
    };
  }

  handleDoubleClick() {
    this.setState({ editing: true });
  }
  …
}
            

Un confort énorme

sur plein de petits trucs

Déstructuration


const { activeCount } = this.props;
…
const { filter: selectedFilter, onShow } = this.props;
            

const [, filters] = output.props.children;
…
const [,, clear] = output.props.children;
            

var { op: a, lhs: { op: b }, rhs: c } = getASTNode();
            

Rest & Spread


function winners(first, runnerUp, ...others) {
  console.log(first, runnerUp, others);
}
winners('alice', 'bob', 'claire', 'david')
// => 'alice', 'bob', ['claire', 'david']

var arr1 = ['one', 'two'], arr2 = ['three', 'four'];
arr1.push(...arr2) // => 4
arr1                // => ['one', 'two', 'three', 'four']

var defaults = { first: 'John', last: 'Doe', age: 42 };
var trainer  = { last: 'Smith', age: 35 };
trainer      = { ...defaults, ...trainer, age: 36 }
// => { first: 'John', last: 'Smith', age: 36 }
            

return [{ id: …, completed: …, text: … }, ...state];
            

<TodoItem key={todo.id} todo={todo} {...actions} />
            

Valeurs par défaut


function todos(state = initialState, action) {
  …
}
            

var [first, second = 'foo'] = ['yo'];
// second === 'foo'

var person = { first: 'Louis', sex: 'M', age: 36 };
var { first, last = 'Doe', age = 42 } = person;
// first === 'Louis', last === 'Doe', age === 36
            

Template strings


var person = { first: 'Thomas', last: 'Anderson', age: 25, nickname: 'Neo' };

// Interpolation de JS quelconque
console.log(`${person.first} aka ${person.nickname}`)
// => 'Thomas aka Neo'

// Multi-ligne !
var markup = `<li>
  ${person.first} ${person.last}, age ${person.age}
</li>`;

// Tagged template strings
function html(statics, ...exprs) {
  return statics.reduce(function(acc, text, index) {
    return acc + text + (exprs[index] || '').replace(/</g, '&lt;');
  }, '').join('');
}

var user = '<script>', comment = '<bgsound>';
html`<li>${user}: <blockquote>${comment}</blockquote></li>`
// => '<li>&lt;script>: <blockquote>&lt;bgsound></blockquote></li>'
            

Fonctions fléchées & this préservé


<TodoTextInput text={todo.text}
 editing={this.state.editing}
 onSave={(text) => this.handleSave(todo.id, text)} />
            

TODO_FILTERS = {
  [SHOW_ALL]: () => true,
  [SHOW_ACTIVE]: todo => !todo.completed,
  [SHOW_COMPLETED]: todo => todo.completed
};
…
atLeastOneCompleted = this.props.todos.some(todo => todo.completed);
            

let & const


const { activeCount } = this.props;
…
const { filter: selectedFilter, onShow } = this.props;
            

const ADD_TODO = 'ADD_TODO';
const DELETE_TODO = 'DELETE_TODO';
const EDIT_TODO = 'EDIT_TODO';
            

for (let index = 0; index < 10; ++index)
  setTimeout(() => console.log(index), 10);
// => Effectivement de 0 à 9, pas 10 x 10, malgré l’asynchrone
            

Litéraux octaux & binaires


0o52     // => 42
0b101010 // => 42
            

Des vrais modules

qui défoncent

Mieux que tous les autres


import * as types from '../constants/ActionTypes';

export function addTodo(text) {
  return { type: types.ADD_TODO, text };
}
            

import React, { PropTypes, Component } from 'react';
import classnames from 'classnames';
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters';
…
export default Footer;
            

export const ADD_TODO = 'ADD_TODO';
export const DELETE_TODO = 'DELETE_TODO';
export const EDIT_TODO = 'EDIT_TODO';
            

L’asynchrone

en version triviale

Promesses


caches.match('/data.json').then(function(response) {
  if (!response) throw Error("No data");
  return response.json();
}).then(function(data) {
  // don't overwrite newer network data
  if (!networkDataReceived) {
    updatePage(data);
  }
}).catch(function() {
  // we didn't get cached data, the network is our last hope:
  return networkUpdate;
}).catch(showErrorMessage).then(stopSpinner);
            

Super manière de les apprivoiser : Promisees

Fonctions asynchrones

var Promise = require("bluebird"); var R = require("ramda"); async function forecastCities() { var forecast = require("notebook")("tonic/forecast/1.0.0"); var promises = R.map(forecast.threeDay, arguments); return R.zipObj(arguments, await Promise.map(promises, R.identity)); } await forecastCities("New York", "San Francisco");

const Promise = require("bluebird");
const R = require("ramda");

await forecastCities("New York", "San Francisco");

async function forecastCities()
{
    var forecast = require("notebook")("tonic/forecast/1.0.0");
    var promises = R.map(forecast.threeDay, arguments);

    return R.zipObj(arguments, await Promise.map(promises, R.identity));
}
            

Voir le résultat (et d’autres encore plus fun)

Encore plus fort

Avec les proxies et ES7

Compréhensions


let results = [
  for (c of customers)
      if (c.city == "Seattle")
        { name: c.name, age: c.age }
];
            

Pour comparaison, en CoffeeScript :


results = ({ name: c.name, age: c.age }
  for c in customers when c.city is "Seattle")
            

Décorateurs


class Meal {
  @readonly
  entree = 'Steak'

  @memoized('5 minutes')
  fullRun() {
    // …
  }
}
            

Proxies


function safeObj(obj) {
  return new Proxy(obj, {
    get(target, propKey, receiver) {
      if (!(propKey in target)) {
          throw new ReferenceError('Unknown property: ' + propKey);
      }
      return Reflect.get(target, propKey, receiver);
    }
  });
}

var o = safeObj({ first: 'Christopher', last: 'Colombus' });
o.frist // => ReferenceError: Unknown property: frist
            

Envie d’en savoir plus ?

On fait des super formations de ouf sur
Git, JavaScript et le dev web front et Node.js.

Merci !

Et que JS soit avec vous


Christophe Porteneuve

@porteneuve

Retrouvez les slides sur bit.ly/deep-dive-es6
Plein de bonnes ressources pour apprendre ES6 et l’explorer