12 KiB
12 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 en interne | Nécessité de mettre en place un pipeline de transpilage |
Architecture Flux | Architecture Flux |
Possibilité de faire des applications "isomorphiques" avec NodeJS | |
"React Native" | |
API stable | |
Virtual DOM | |
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
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 |
Concepts généraux
Ember CLI
Routes
Templates
Modèles
Contrôleurs
Composants
Ember CLI
Générer une nouvelle application
npm install ember-cli -g
ember new my-app
cd my-app
ember serve
Routes
Générer une nouvelle route
ember generate route foo
Templates
{{!-- Interpolation de variable --}}
{{ myVar }}
{{!-- Utilisation d'un "helper" --}}
{{helper params... }}
{{!-- Définition d'un "block" --}}
{{#block params... }}
...
{{/block}}
Modèles
Générer un nouveau modèle
ember generate model 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.