16 KiB
16 KiB
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)
- Publication: Septembre 2013
- Mainteneurs principaux: @GianlucaGuarini et @tipiirai (projet communautaire)
- License: MIT
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>
<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
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.