formations/developpement/intro_javascript/presentation/slides.md

16 KiB
Raw Permalink Blame History

Frameworks Javascript - Tour d'horizon

William Petit - S.C.O.P. Cadoles


Les frameworks

RiotJS

ReactJS

EmberJS


RiotJS

Présentation générale

Concepts généraux

Exercice


Présentation générale (1)


Présentation générale (2)

Avantages Inconvénients
Une librairie et pas un framework Une librairie et pas un framework
Projet communautaire Projet communautaire
Orientée "composant"
API minimaliste
Prêt à l'emploi (pas de transpilage nécessaire en développement)
Routeur "frontend" disponible

Concepts généraux

Composants ("tags")

Observable


Composants

Structure et utilisation d'un composant

Templating

Styles de composant

Évènements du DOM

Cycle de vie du composant


Structure d'un composant

<!-- components/my-tag.tag -->

<!-- Racine du Tag -->
<my-tag>

  <!-- Définition du DOM -->
  <span>{ opts.foo }</span>
  
  <!-- Style du composant -->
  <style>
    :scope {
      display: block
    }
  </style>
  
  <!-- Script du composant -->
  <script>
    this.on('mount', () => {
      this.opts.foo = "bar";
    });
  </script>
  
</my-tag>

Utilisation du composant

Compilation dans le navigateur

<html>
  <body>
  	
    <!-- Utilisation du composant dans notre page HTML -->
    <my-tag></my-tag>
    
    <!-- Inclusion de notre "tag" -->
    <script type="riot/tag" src="components/my-tag.tag"></script>
    
    <!-- Inclusion de la librairie Riot + compilateur -->
    <script type="text/javascript" src="js/riot+compiler.js"></script>
    
    <!-- Montage des composants Riot -->
    <script type="text/javascript">
      var opts = {}; // Options passées à tous les tags
      riot.mount('*', opts)
    </script>
    
  </body>
</html>

Templating

<my-tag>

  <!-- Interpolation simple -->
  <span>{ foo }</span> <!-- <span>bar</span> -->
  
  <!-- Boucles -->
  <ul>
    <li each={ item in myItems }>{ item.text }</li>
  </ul>
  
  <!-- Condition avec appel de méthode -->
  
  <span if={ isFooBar() }>foo est égal à "bar" !</span>
  
  <script>
    
    this.foo = "bar"
    
    this.myItems = [
    	{ text: "Hello World" },
        { text: "Foo Bar" }
    ];
    
    isFooBar() {
      return this.foo == "bar";
    }
    
  </script>
  
</my-tag>

Styles de composant

Feuille de style

Styles et classes dynamiques


Feuille de style

<my-tag>

  <!-- Définition du DOM -->
  <span id="foo">Foo</span>
  <div class="bar">Bar</div>
  
  <!-- Style du composant -->
  <style>
    :scope {
      display: block
    }
    #foo {
      color: red;
    }
    .bar {
      background: blue;
    }
  </style>
  
</my-tag>

Styles et classes dynamiques

<my-tag>

  <!-- Définition du DOM -->
  <span style={ fooStyles }>Foo</span>
  <div class={ barClasses }>Bar</div>
  
  
  <script>
    
    this.fooStyles = {
      color: "red"
    }
    
    this.barClasses = {
      "bar": true, // Classes conditionnelles
      "foo": false
    }
    
  </script>
  
</my-tag>

Évènements du DOM

<my-tag>

  <button onclick={ onButtonClick }>Click me !</button>
  
  <script>
    
    onButtonClick(evt) {
    
      // Si l'élément à l'origine se trouve dans une boucle each={ ...}, 
      // evt.item pointera vers l'élément courant
      
      console.log(evt);
      
    }
    
  </script>
  
</my-tag>

Cycle de vie du composant

<my-tag>
  
  <script>
    this.on('before-mount',   () => console.log('before-mount'));
    this.on('mount',          () => console.log('mount'));
    this.on('update',         () => console.log('update'));
    this.on('updated',        () => console.log('updated'));
    this.on('before-unmount', () => console.log('before-unmount'));
    this.on('unmount',        () => console.log('unmount'));
  </script>
  
</my-tag>

Observable (1)

Déclaration d'un "service"

// services/auth.js
class MyAuthService {

  constructor() {
    riot.observable(this)
    this.user = null;
  }
  
  login(user, password) {
   
   var form = new FormData();
   form.append('login', login);
   form.append('password', password);
   
   return fetch('/login', { method: 'POST', form: form })
     .then(res => res.json())
     .then(user => {
       this.user = user;
       this.trigger('loggedIn', user);
     })
   ;
  }
  
  isLoggedIn() {
    return this.user != null;
  }
  
}

const myAuth = new MyAuthService();

Observable (2)

Utilisation du service

<my-app></my-app>

<!-- Tag "inline" -->
<script type="riot/tag">

  <my-app>
    <button if={ !user } 
      onclick={ login }>
      Login
    </button>
    <span if={ user }>User: { user.name }</span>
  </my-app>

  <script>
    this.user = null;
    
    this.on('mount', () => {
      this.opts.auth.on('loggedIn', user => {
        this.user = user;
        this.update();
      });
    });
    
    login() {
      this.opts.auth.login();
    }
    
  </script>

</script>

<script type="text/javascript" src="services/auth.js"></script>
<script type="text/javascript">riot.mount('*', { auth: myAuth })</script>

Exercice

Implémenter une application minimaliste de gestion de tâches avec Riot. Cette application doit comprendre les fonctionnalités suivantes:

  • Ajouter une nouvelle tâche (champs texte) avec une priorité parmi les suivantes: "haute", "moyenne", "basse" et un statut: "en cours" ou "terminé"
  • Marquer une tâche comme "terminée".
  • Afficher la liste des tâches en attente, triées par priorité décroissante + tâches terminées en dernier.
  • Les tâches doivent être persistées dans le "Local Storage" du navigateur.

ReactJS

Présentation générale

Concepts généraux

Prise en main


Présentation générale (1)

  • Publication: Mars 2013
  • Mainteneur principal: Facebook
  • License: MIT

Présentation générale (2)

Avantages Inconvénients
Grande communauté, très active Changement de license puis rétropédalage en 2017
Maintenu et utilisé par Facebook Nécessité de mettre en place un pipeline de transpilage
Architecture Flux Architecture Flux
Possibilité de faire des applications "isomorphiques" avec NodeJS
Projet "React Native"
API stable
Virtual DOM (voir cet article)
Moteurs "lightweight" compatibles

Concepts généraux

Composants

L'architecture Flux


Composants

Structure d'un composant

Props

State

Évènements du DOM

Cycle de vie d'un composant


Structure d'un composant

// components/hello-world.js

import React from 'react'

class HelloWorld extends React.Component {

  render() {
    return (
      <div className='hello-world'>
        <h1>Hello World</h1>
        <p>Welcome to React</p>
      </div>
    )
  }

}

export default HelloWorld


Props

// components/props-example.js

import React from 'react'

class PropsExample extends React.Component {

  render() {
    return (
      <span>{ this.props.text }</span>
    )
  }

}

export default PropsExample

// app.js

import React from 'react'
import ReactDOM from 'react-dom'
import PropsExample from './components/props-example'

const mountpoint = document.getElementById('props-example');

ReactDOM.render(<PropsExample text="foo bar" />, mountpoint)


State

// components/clock.js

import React from 'react'

class Clock extends React.Component {

  constructor(props) {
    // On fait appel au constructeur de la classe
    // parente
    super(props)
    
    // Initialisation du "state" du composant
    this.state = {
      time: new Date()
    }
    // On appelle la méthode tick() du composant
    // toutes les secondes
    setInterval(this.tick.bind(this), 1000);
  }
	
  // Méthode de rendu du composant
  render() {
    return (
      <div>Time: { this.state.time.toString() }</div>
    )
  }
  
  // La méthode tick() met à jour le state du composant avec
  // la date courante
  tick() {
    this.setState({ time: new Date() });
  }

}


export default Clock


Évènements du DOM

// components/counter.js
import React from 'react'

class Counter extends React.Component {

  constructor(props) {
    // On fait appel au constructeur de la classe
    // parente
    super(props)

    // Initialisation du "state" du composant
    this.state = {
      count: 0
    }
  }

  // Méthode de rendu du composant
  render() {
    return (
      <div>
      	Count: <span>{ this.state.count }</span>&nbsp;
        <button onClick={ () => this.increment() }>+1</button>
        <button onClick={ () => this.decrement() }>-1</button>
      </div>
    )
  }

  // La méthode increment() incrémente la valeur du compteur de 1
  increment() {
    this.setState(prevState => ({ count: prevState.count+1 }))
  }

  // La méthode decrement() incrémente la valeur du compteur de 1
  decrement() {
    this.setState(prevState => ({ count: prevState.count-1 }))
  }

}

export default Counter

Cycle de vie d'un composant

// components/clock-lifecycle.js
import React from 'react'

class Clock extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      time: new Date()
    }
  }

  componentDidMount() {
    console.log('mount')
    this.intervalId = setInterval(this.tick.bind(this), 1000);
  }

  componentWillUnmount() {
    console.log('unmount')
    clearInterval(this.intervalId);
  }

  render() {
    return (
      <div>Time: { this.state.time.toString() }</div>
    )
  }

  tick() {
    this.setState({ time: new Date() });
  }

}

export default Clock

L'architecture Flux

center

Voir une description des concepts


Exercice

Implémenter une application minimaliste de gestion de tâches avec React (sans l'architecture Flux). Cette application doit comprendre les fonctionnalités suivantes:

  • Ajouter une nouvelle tâche (champs texte) avec une priorité parmi les suivantes: "haute", "moyenne", "basse" et un statut: "en cours" ou "terminé"
  • Marquer une tâche comme "terminée".
  • Afficher la liste des tâches en attente, triées par priorité décroissante + tâches terminées en dernier.
  • Les tâches doivent être persistées dans le "Local Storage" du navigateur.

EmberJS

Présentation générale

Concepts généraux

Initialisation d'un projet

Exercice


Présentation générale (1)

  • Publication: Décembre 2011
  • Mainteneur principal: Ember Core Team (projet communautaire)
  • License: MIT

Présentation générale (2)

Avantages Inconvénients
"Clés en main" "Opiniatre"
API stable Structure MVC classique
Long-term support (1 an)
Documentation exhaustive
Ember Data
Ember Inspector
Ember CLI

Concepts généraux

Ember CLI

Routes

Templates

Modèles

Contrôleurs


Ember CLI

Générer une nouvelle application

npm install ember-cli -g
ember new my-app
cd my-app
ember serve

Routes (1)

Générer une nouvelle route

ember generate route posts

Routes (2)

Attacher un/plusieurs modèles à une route

// app/routes/posts.js
import Route from '@ember/routing/route';

export default Route.extend({
  model() {
    return this.get('store').findAll('post');
  }
});

Routes (3)

Effectuer une redirection

// app/routes/index.js
import Route from '@ember/routing/route';

export default Route.extend({
  beforeModel() {
    this.transitionTo('posts');
  }
});

Templates (1)

Syntaxe générale

{{!-- Interpolation de variable --}}
{{ myVar }}

{{!-- Utilisation d'un "helper" --}}
{{helper params... }}

{{!-- Définition d'un "block" --}}
{{#block params... }}
 ...
{{/block}}

{{!-- Helpers imbriqués --}}
{{sum (multiply 2 4) 2}}

Templates (2)

"Blocks" de base

{{!-- Condition --}}
{{if myVar}}
  Maybe...
{{else}}
  Or not.
{{/if}}

{{!-- Boucle --}}
{{#each people as |person|}}
  <li>Hello, {{person.name}}!</li>
{{/each}}

Templates (3)

"Helpers" spécifiques

{{!-- Affiche myVar[key] (accès dynamique aux variables exposées dans le controleur) --}}
{{get myVar "key"}} 

{{!-- Affiche le contenu de la (sous) route --}}
{{outlet}}

{{!-- Modèle exposé par la route --}}
{{model}}

{{!-- Affiche un lien vers la route donnée --}}
{{link-to routeName}}

{{!-- Attacher une action à un élément (onclick) --}}
<button {{action "myAction"}}>Do it !</button>

Contrôleurs (1)

Générer un contrôleur

ember generate controller posts

Contrôleurs (2)

Exposer des variables pour le template

import Controller from '@ember/controller';

export default Controller.extend({
  foo: "bar" // {{ foo }} dans le template associé
});

Contrôleurs (3)

Actions

import Controller from '@ember/controller';

export default Controller.extend({
  
  foo: "bar"

  actions: {
    myAction: function() {
      // On modifie la valeur de "foo"
      // Si "foo" est utilisé dans le template, il sera automatiquement mis à jour
      this.set("foo", "hello world");
    }
  }
});


Modèles (1)

Générer un nouveau modèle

ember generate model post

Modèles (2)

Définition d'un modèle

// app/models/comment.js
import DS from 'ember-data';

export default DS.Model.extend({
  
  // Simple attributs
  text: DS.attr('string'),
  
  // Relations
  post: DS.belongsTo('post')
  
});

// app/models/post.js
import DS from 'ember-data';

export default DS.Model.extend({
  
  // Simples attributs
  title: DS.attr('string'),
  content: DS.attr('string'),
  
  // Relations
  comments: DS.hasMany('comment')
  
});

Modèles (3)

Manipuler une instance de modèle

// app/controllers/posts.js

// Créer une nouvelle instance
var post = store.createRecord('post', {
  title: 'Mon article',
  content: 'Lorem ipsum'
});

// Modifier l'instance
post.set('title', 'Foo bar');

// Sauvegarde de l'instance
post.save().then(() => console.log("Post saved !"));

// Supprimer l'instance
post.destroyRecord().then(() => console.log("Post deleted !"));


Modèles (4)

Effectuer des requêtes sur le "store"

// app/controllers/posts.js

// Récupérer toutes les instances de "post"
var posts = this.get('store').findAll('post');

// Requête avec filtre
var posts = this.get('store').query('post', { 
  filter: {
    title: "Foo Bar"
  }
});


Exercice

Implémenter une application minimaliste de gestion de tâches avec EmberJS. Cette application doit comprendre les fonctionnalités suivantes:

  • Ajouter une nouvelle tâche (champs texte) avec une priorité parmi les suivantes: "haute", "moyenne", "basse" et un statut: "en cours" ou "terminé"
  • Marquer une tâche comme "terminée".
  • Afficher la liste des tâches en attente, triées par priorité décroissante + tâches terminées en dernier.
  • Les tâches doivent être persistées dans le "Local Storage" du navigateur.

Licence

CC BY-NC-SA 3.0 FR

Creative Commons - Attribution - Pas dUtilisation Commerciale - Partage dans les Mêmes Conditions 3.0 France