fullstack/vuejs2
partie 1 - partie 2 - partie 3
Routage des URL front-end
Problématique du routage back-end / front-end
Un serveur de production héberge le plus souvent le back-end et le front-end. S’il est destiné à être utilisé sur Internet, on souhaitera généralement que toutes les requêtes soient faites sur les ports HTTP standards 80 (http://) ou 443 (https://) afin d’éviter d’être bloqué par des pare-feux
Le serveur frontal (Apache, Nginx, Node/Express, IIS, etc.) effectue en premier une forme de méta-routage : pour chaque nom de domaine, par exemple yesno.wtf
, un fichier de configuration propre à ce domaine fournit les règles qui lui permettent de distinguer les url back-end des url front-end. Par exemple l’url http://yesno.wtf/api
est un url de back-end, alors que https://yesno.wtf/
ou https://yesno.wtf/assets/application-dc53a8eb45b5db278e3c1ba040413b83.css
sont des url front-end ; cette sélection est faite à partir du chemin d’accès de l’url, c’est à dire la partie située après le protocole et le nom de domaine, et avant l’ancre (#)
les url back-end sont redirigés par le serveur fontal vers des serveurs spécifiques d’application, par exemple Python/Django/wsgi, ou NodeJS/natif. Ils sont associés à des webservices, ou produisent des pages web à partir de templates (Django, etc.), ou produisent des documents pdf, etc. Les verbes HTTP utilisés sont de tous types : GET, POST, PUT, etc.
Par exemple, l’urlhttps://yesno.wtf/api
est associé à un webservice ; son chemin/api
le fait classer par le serveur frontal comme un url de back-end, pour cette applicationyesno.wtf
les url front-end, comme
https://yesno.wtf/
, ouhttps://yesno.wtf/assets/application-dc53a8eb45b5db278e3c1ba040413b83.css
sont servis comme de simples fichiers statiques que le client charge et interprète. Seules des requêtes GET et OPTIONS sont effectuées. Des serveurs comme Apache ou Nginx se chargent souvent de servir directement ces fichiers statiques, de façon très efficace.
Le back-end et le front-end appliquent ensuite chacun leur propre politique de routage des url, mais seulement pour les url qui les concernent, du fait du tri initial effectué par le serveur frontal. Côté back-end, on connait le système de routage appliqué par Node/Express ou par Python/Django. Côté front-end, après le chargement de la première page et le lancement du script associé, chaque changement de la variable javascript window.location
est détecté et déclenche la mise en oeuvre du système de routage front-end.
Le routage front-end : push-state ou hash-based
La partie chemin d’accès d’une url (après le protocole, le nom de domaine et le port) a la forme suivante :
/<chemin>/.../<chemin>/#/<chemin>/.../<chemin>/
Dans tous les framework front-end modernes comme React, Angular ou Vue, le choix est proposé d’opérer le routage front-end, soit sur la partie avant le hash #
, soit sur la partie après le hash.
Quand on prend en compte seulement la partie avant le hash, on parle de mode historique HTML5, ou push-state ; par exemple : http://mon.domaine/register
Quand on prend en compte la partie après le hash, on parle de routage en mode hash ; par exemple : http://mon.domaine/client/#/register
En mode push-state :
- les url sont plus naturels
- chaque changement est mémorisé dans l’historique de navigation
- chaque changement d’url amène potentiellement un rechargement de la page
En mode hash :
- les url sont moins naturels
- les changements ne sont pas mémorisés dans l’historique de navigation
- les changements d’url n’amène pas de rechargement de la page
Vue-router
Le routage des url par le client n’est pas assuré par VueJS lui-même, mais par un module tiers, généralement vue-router
.
<template>
<div id="app">
<a href='#/componenta'>composant A</a>
<a href='#/componentb'>composant B</a>
<router-view></router-view>
</div>
</template>
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import ComponentA from '@/components/ComponentA'
import ComponentB from '@/components/ComponentB'
Vue.use(Router)
const router = new Router({
mode: 'hash',
base: '/',
routes: [
{
path: '/componenta',
name: 'componenta',
component: ComponentA,
},
{
path: '/componentb',
name: 'componentb',
component: ComponentB,
},
]
})
export default router
components/ComponentA.vue
<template>
<p>Hello composant A</p>
</template>
components/ComponentB.vue
<template>
<p>Hello composant B</p>
</template>
main.js
import router from './router'
...
new Vue({
...
router
}).$mount('#app')
Algorithmique
- à chaque fois qu’un nouvel url est poussé (
router.push
, lien<router-link>
), ou modifié (router.go
), la table de routage est consultée et la première règle qui s’applique est prise en compte. Toutes les autres sont ignorées. - le tag
<router-view />
de plus haut niveau est remplacé par le composant spécifié par la règle active. Selon la valeur deprops
(booléen, objet, fonction), les props du composant sont affectées (voir plus loin) - si la route contient des
children
, le même algorithme est appelé récursivement pour peupler les<router-view />
emboités
Le plugin Vue
dans le navigateur permet de suivre les associations entre routes et instances de <router-view />
Passage des props
aux composants d’une route
Mode booléen
Indiquer props: true
passe au composant référencé par la route, tout l’objet route.params
en tant que props
. Par exemple :
{
path: '/user/:id/posts/:postid',
component: Post,
props: true
}
Ici c’est l’objet { id: <id>, postid: <postid> }
qui est passé comme props
au composant Post
associé à la route
Mode objet
Quand props
dans la définition de la route est un objet, c’est lui qui sera passé tel-quel au composant
Mode fonction
Quand props
dans la définition de la route est une fonction, c’est la valeur de cette fonction qui sera passée au composant. Cette fonction prend comme argument l’objet route
. Par exemple :
props: (route) => ({ query: route.query.q })
The Full Navigation Resolution Flow
- Navigation triggered.
- Call beforeRouteLeave guards in deactivated components.
- Call global beforeEach guards.
- Call beforeRouteUpdate guards in reused components.
- Call beforeEnter in route configs.
- Resolve async route components.
- Call beforeRouteEnter in activated components.
- Call global beforeResolve guards.
- Navigation confirmed.
- Call global afterEach hooks.
- DOM updates triggered.
- Call callbacks passed to next in beforeRouteEnter guards with instantiated instances.
Vuex - gestion centralisée de l’état
Quand faut-il utiliser une gestion centralisée d’état ?
Linus Borg: " Personally, I try and use local state as long as it works - which means, as long as this state is really only needed by one component (and it’s children). As soon as the data really is needed in more than one place, I move it to the store."
Principes généraux
un
store
vuex contient l’état partagé d’une applicationcontrairement à l’utilisation d’un objet global, les expressions calculées qui sont basées sur l’état du store, seront mises à jour de façon réactive, dans tous les composants
l’état d’un
store
doit être modifié par appel de fonctions spéciales demutation
, qui permettent notamment de tracer l’évolution de l’état. Il est interdit de modifier directement l’état d’unstore
Exemple :
store.js
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
const state = {
...
messages: undefined,
...
}
const mutations = {
...
ADD_USER_MESSAGE (state, message) {
// add message to state.messages
Vue.set(state.messages, message.id, message)
},
...
}
const actions = {
...
POST_USER_MESSAGE async function({ commit, state }, message) {
let response = await axios({
method: 'post',
url: MESSAGES_URL,
data: message,
})
commit('ADD_USER_MESSAGE', response.data)
},
...
}
export default new Vuex.Store({
state,
mutations,
actions,
})
Messages.vue
<template>
<div id="app">
...
<button @click="postMessage">Post message</button>
...
</div>
</template>
<script>
export default {
...
methods: {
postMessage: function() {
this.$store.dispatch("POST_USER_MESSAGE", message)
}
},
...
}
</script>
Actions
- les actions peuvent contenir des méthodes asynchrones
- les actions commitent des mutations
Réactivité des tableaux
https://vuejs.org/v2/guide/list.html#Caveats
Des limitations de JavaScript empêchent Vue de détecter certains changements d’un tableau, que ce soit dans un état local ou dans un store Vuex
- modification d’une valeur à un index, e.g.
vm.items[indexOfItem] = newValue
- modification directe de la taille, e.g.
vm.items.length = newLength
Parades :
Vue.set(example1.items, indexOfItem, newValue)
Vue.delete(example1.items, indexOfItem)
Vue.delete(example1.items, key)