Maison > interface Web > js tutoriel > Cycle de vie de Vue et implémentation du code source (code)

Cycle de vie de Vue et implémentation du code source (code)

不言
Libérer: 2018-09-07 16:57:48
original
2273 Les gens l'ont consulté

Le contenu que cet article vous apporte concerne le cycle de vie et l'implémentation du code source (code) de Vue. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Grâce à l'apprentissage, nous avons appris toute la syntaxe de base de Vue, notamment :

1, syntaxe {{Mustache}}
2. v-si, v-else, v-else-if, v-show
3. v-pour
4. v-bind
5. v-modèle
6. v-on

Si vous avez déjà gardé ces grammaires à l'esprit, continuez à lire. Si vous ne maîtrisez pas très bien ces grammaires, j'espère que vous réviserez le contenu des précédentes.

Dans ce chapitre, nous étudions le cycle de vie de Vue. Jetons d'abord un coup d'œil à la définition du cycle de vie de Vue.

Chaque instance de Vue passe par une série de processus d'initialisation lors de sa création - par exemple, vous devez configurer la surveillance des données, compiler des modèles, monter l'instance sur le DOM et mettre à jour le DOM lorsque les données changent. , etc. Parallèlement, certaines fonctions appelées life cycle hooks seront également exécutées au cours de ce processus, ce qui donnera aux utilisateurs la possibilité d'ajouter leur propre code à différentes étapes.

Voici les informations de description fournies sur le site officiel de Vue. En termes simples : Dans le processus de Vue depuis la création d'une instance jusqu'à sa mort complète, une série de méthodes sera exécutée pour correspondre à l'actuelle. Vue Status, nous appelons ces méthodes : hooks de cycle de vie . Jetons un coup d'œil au diagramme du cycle de vie ci-dessous :

Cycle de vie de Vue et implémentation du code source (code)

Dans le diagramme ci-dessus, un total de 8 fonctions hook de cycle de vie sont affichées. Cette fonction décrit l'intégralité du cycle d'exécution de Vue. Pour l'instant, la version de Vue est -2.5.16. Vue a un total de 11 hooks de cycle de vie En plus des 8 actuels, il existe également 3 hooks de cycle de vie pour le composant . Jetons un coup d'œil à toutes les explications de la fonction hook, et avec le diagramme ci-dessus, nous pouvons mieux comprendre le cycle d'exécution de Vue.

1. beforeCreate : appelé après l'initialisation de l'instance et avant la configuration de l'observateur de données et de l'événement événement/observateur.
2. créé : appelé immédiatement après la création de l'instance. À cette étape, l'instance a effectué la configuration suivante : observateur de données, opérations sur les propriétés et les méthodes, et rappels d'événements de surveillance/d'événement. Cependant, la phase de montage n'a pas encore commencé et l'attribut $el n'est pas visible pour le moment.
3. beforeMount : Appelé avant le montage : la fonction de rendu associée est appelée pour la première fois.
4. monté : el est remplacé par le vm.$el nouvellement créé et le hook est appelé après avoir été monté sur l'instance. Si l'instance racine monte un élément dans le document, vm.$el est également dans le document lorsque Mounted est appelé (PS : notez que Mounted ne promet pas que tous les composants enfants seront également montés ensemble. Si vous souhaitez attendre que l'intégralité de la vue est rendue Terminé, vous pouvez remplacer monté par vm.$nextTick :). vm.$nextTickCela sera expliqué en détail dans les chapitres suivants. Tout le monde doit savoir cette chose ici.
5. beforeUpdate : appelé lorsque les données sont mises à jour, ce qui se produit avant que le DOM virtuel ne soit corrigé. Cela convient pour accéder au DOM existant avant la mise à jour, par exemple en supprimant manuellement un écouteur d'événement ajouté.
6. mis à jour : ce hook sera appelé une fois que le DOM virtuel aura été restitué et corrigé en raison de modifications de données. Lorsque ce hook est appelé, le composant DOM a été mis à jour, vous pouvez donc désormais effectuer des opérations qui dépendent du DOM. Dans la plupart des cas, vous devez cependant éviter de changer d’état pendant cette période. Si vous souhaitez répondre aux changements d'état, il est généralement préférable d'utiliser plutôt des propriétés calculées ou des observateurs (PS : les propriétés calculées et les observateurs seront présentés dans les chapitres suivants).
7. activé : appelé lorsque le composant keep-alive est activé (PS : lié au composant, keep-alive vous sera présenté lors de l'explication du composant).
8. désactivé : appelé lorsque le composant keep-alive est désactivé (PS : lié au composant, keep-alive vous sera présenté lors de l'explication du composant).
9. beforeDestroy : appelé avant la destruction de l'instance. A cette étape, l'instance est toujours entièrement disponible.
10. destroy : Appelé après la destruction de l'instance Vue. Lorsqu'il est appelé, tout ce qui est pointé par l'instance Vue ne sera pas lié, tous les écouteurs d'événements seront supprimés et toutes les instances enfants seront détruites.
11. errorCaptured (nouveau dans 2.5.0+) : appelé lorsqu'une erreur d'un composant descendant est capturée. Ce hook reçoit trois paramètres : l'objet d'erreur, l'instance du composant où l'erreur s'est produite et une chaîne contenant des informations sur la source de l'erreur. Ce hook peut renvoyer false pour empêcher l’erreur de se propager davantage vers le haut.

Voici tous les hooks du cycle de vie dans Vue (2.5.16). Pour que tout le monde comprenne plus facilement, voyons comment créer de à Destruction est implémentée. Vous pouvez cliquer ici pour télécharger le dernier code de Vue.

Jetons-y d’abord un bref coup d’œilVue源代码的基础结构.

.
├── BACKERS.md ├── LICENSE
├── README.md├── benchmarks
├── dist
├── examples
├── flow
├── node_modules
├── package.json├── packages
├── scripts
├── src
├── test
├── types
└── yarn.lock
Copier après la connexion

Il s'agit du répertoire de premier niveau après avoir téléchargé le code, dist文件夹下为Vue编译之后的代码,我们平时引入的Vue.js文件都在这里, Vue使用了flow作为JavaScript静态类型检查工具,相关的代码都在flow文件夹下面, scripts文件夹下面是代码构建的相关配置,Vue主要使用Rollup进行的代码构建, src文件夹下面就是所有Vue的源代码. Nous ne décrirons pas trop d'autres contenus ici, mais concentrons-nous sur notre thème, Comment le code du cycle de vie de Vue est implémenté, jetons un œil au dossier src.

.
├── compiler :Vue编译相关
├── core    :Vue的核心代码
├── platforms   :web/weex平台支持,入口文件
├── server  :服务端
├── sfc :解析.vue文件
└── shared  :公共代码
Copier après la connexion

Il s'agit de la structure de répertoires sous notre dossier src, et l'endroit où notre Vue est générée se trouve dans /src/core/instance/index.js.

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }  this._init(options)
}
Copier après la connexion

On peut voir : Vue est une méthode, un constructeur implémenté à l'aide de Function, nous ne pouvons donc créer des instances de Vue que via new. Ensuite, initialisez Vue via la méthode Vue实例 de _init. _init est un prototype implémenté par Vue via 原型属性. Jetons un coup d'œil à sa mise en œuvre de la méthode _init.

Sous le dossier /src/core/instance/init.js, Vue implémente la méthode _init

Vue.prototype._init = function (options?: Object) {    ...
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')    ...
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
 }
Copier après la connexion

Je regarde principalement le code lié à son cycle de vie On peut voir que Vue先调用了initLifecycle(vm)、initEvents(vm)、initRender(vm)ce Three. méthodes d'initialisation de 生命周期、事件、渲染函数 Ces processus se produisent dans Vue初始化的过程(_init方法)中 et avant d'appeler beforeCreate钩子.

Ensuite, Vue appelle callHook (vm: Component, hook: string) via la méthode 钩子函数(hook), qui reçoit vm(Vue实例对象),hook(钩子函数名称) pour exécuter 生命周期函数. Dans Vue, presque toutes les fonctions hook (sauf ) dans Vue sont appelées via errorCaptured. Jetons un coup d'œil au code de callHook (vm: Component, hook: string), sous : callHook/src/core/instance/lifecycle.js

Sa logique est également très simple D'après le passé dans
export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()  const handlers = vm.$options[hook]  
  if (handlers) {    
  for (let i = 0, j = handlers.length; i < j; i++) {      
  try {
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }  if (vm._hasHookEvent) {
    vm.$emit(&#39;hook:&#39; + hook)
  }
  popTarget()
}
Copier après la connexion
, le tableau de fonctions de rappel correspondant est obtenu à partir de. l'instance (

sous hook), puis faciliter l'exécution. /packages/vue-template-compiler/browser.jsLIFECYCLE_HOOKS appelle ensuite

après avoir initialisé

À ce moment : 生命周期、事件、渲染函数Nous n'avons aucun moyen d'obtenir beforeCreate钩子 et d'autres données . data、propsAprès avoir appelé

,

ces trois méthodes sont utilisées pour initialiser beforeCreate钩子 et ainsi de suite. Une fois ces initialisations terminées, Vue调用了initInjections(vm)、initState(vm)、initProvide(vm) est appelé à ce moment : data、props、watcher Nous pouvons. obtenons déjà des données telles que created钩子函数, mais Vue n'a pas démarré , nous ne pouvons donc pas encore accéder au DOM (PS : nous pouvons y accéder via data、props, ce que nous expliquerons en détail dans les chapitres suivants). 渲染DOMvm.$nextTickAprès avoir appelé

,

Vue commence à monter le DOM created钩子 et exécute Le montage du DOM dans Vue se fait via le prototype Les méthodes vont et viennent. vm.$mount(vm.$options.el) La déclaration de la méthode prototype est dans Vue.prototype.$mount Regardons l'implémentation de ce code : Vue.prototype.$mount/src/platforms/web/entry-runtime-with-compiler.js

La fonction principale de cette partie du code :
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)  
  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== &#39;production&#39; && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )    return this
  }  const options = this.$options  
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template    
    if (template) {      
    if (typeof template === &#39;string&#39;) {        
    if (template.charAt(0) === &#39;#&#39;) {
          template = idToTemplate(template)          
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== &#39;production&#39; && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {        if (process.env.NODE_ENV !== &#39;production&#39;) {
          warn(&#39;invalid template option:&#39; + template, this)
        }        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }    if (template) {      
    /* istanbul ignore if */
      if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
        mark(&#39;compile&#39;)
      }      
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns      
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
        mark(&#39;compile end&#39;)
        measure(`vue ${this._name} compile`, &#39;compile&#39;, &#39;compile end&#39;)
      }
    }
  }  return mount.call(this, el, hydrating)
}
/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */function getOuterHTML (el: Element): string {
  if (el.outerHTML) {    
  return el.outerHTML
  } else {    
  const container = document.createElement(&#39;p&#39;)
    container.appendChild(el.cloneNode(true))    
    return container.innerHTML
  }
}
Copier après la connexion
est de. effectuer

. Comme le montre le code ci-dessus, template模板的解析el ne peut pas être monté sur des balises racine telles que et . body Déterminez ensuite s'il y a html, puis déterminez s'il y a . Le modèle peut être render函数 -> if (!options.render) {...}template, string类型的id. Sinon, analysez DOM节点 comme . Il ressort du code ci-dessus que el que nous utilisions template ou que nous passions , il finira par analyser l'intégralité du modèle 单文件组件(.Vue) sous la forme de la fonction el、template属性. render D'après notre diagramme, nous pouvons voir qu'une fois l'analyse du modèle terminée,

sera appelé. Alors, où est-il appelé

 ? Regardons en bas. beforeMount钩子La méthode prototype a une conception réutilisable. Sous beforeMount钩子, il y a un tel morceau de code$mount.

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
Copier après la connexion

这是一个公共的挂载方法,目的是为了被runtime only版本的Vue直接使用,它调用了mountComponent方法。我们看一下mountComponent方法的实现,实现在/src/core/instance/lifecycle.js下。

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el  
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode    
    if (process.env.NODE_ENV !== &#39;production&#39;) {      
    /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== &#39;#&#39;) ||
        vm.$options.el || el) {
        warn(          
        &#39;You are using the runtime-only build of Vue where the template &#39; +          
        &#39;compiler is not available. Either pre-compile the templates into &#39; +          
        &#39;render functions, or use the compiler-included build.&#39;,
          vm
        )
      } else {
        warn(          
        &#39;Failed to mount component: template or render function not defined.&#39;,
          vm
        )
      }
    }
  }
  callHook(vm, &#39;beforeMount&#39;)

  let updateComponent  
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
    updateComponent = () => {      
    const name = vm._name      
    const id = vm._uid      
    const startTag = `vue-perf-start:${id}`      
    const endTag = `vue-perf-end:${id}`

      mark(startTag)      
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }  
  // we set this to vm._watcher inside the watcher&#39;s constructor
  // since the watcher&#39;s initial patch may call $forceUpdate (e.g. inside child
  // component&#39;s mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, &#39;mounted&#39;)
  }  
  return vm
}
Copier après la connexion

由上面的代码可以看出在执行vm._render()之前,调用了callHook(vm, &#39;beforeMount&#39;),这个时候相关的 render 函数首次被调用,调用完成之后,执行了callHook(vm, &#39;mounted&#39;)方法,标记着el 被新创建的 vm.$el 替换,并被挂载到实例上。

然后就进入了我们页面正常交互的时间,也就是beforeUpdateupdated这两个回调钩子的执行时机。这两个钩子函数是在数据更新的时候进行回调的函数,Vue在/src/core/instance/lifecycle.js文件下有一个_update的原型声明:

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this    
    if (vm._isMounted) {
      callHook(vm, &#39;beforeUpdate&#39;)
    }    const prevEl = vm.$el
    const prevVnode = vm._vnode    
    const prevActiveInstance = activeInstance
    activeInstance = vm
    vm._vnode = vnode    
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {      
    // initial render
      vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false 
        /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      )      
      // no need for the ref nodes after initial patch
      // this prevents keeping a detached DOM tree in memory (#5851)
      vm.$options._parentElm = vm.$options._refElm = null
    } else {      
    // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance    
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }    if (vm.$el) {
      vm.$el.__vue__ = vm
    }    
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }    
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent&#39;s updated hook.
  }
Copier après la connexion

我们可以看到在如果_isMountedture的话(DOM已经被挂载)则会调用callHook(vm, &#39;beforeUpdate&#39;)方法,然后会对虚拟DOM进行重新渲染。然后在/src/core/observer/scheduler.js下的flushSchedulerQueue()函数中渲染DOM,在渲染完成调用callHook(vm, &#39;updated&#39;),代码如下:。

/**
 * Flush both queues and run the watchers.
 */function flushSchedulerQueue () { ...
  callUpdatedHooks(updatedQueue) ...}function callUpdatedHooks (queue) {
  let i = queue.length  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm    
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, &#39;updated&#39;)
    }
  }
}
Copier après la connexion

当Vue实例需要进行销毁的时候回调beforeDestroy 、destroyed这两个函数钩子,它们的实现是在/src/core/instance/lifecycle.js下的Vue.prototype.$destroy中:

  Vue.prototype.$destroy = function () {
    const vm: Component = this    
    if (vm._isBeingDestroyed) {      
    return
    }
    callHook(vm, &#39;beforeDestroy&#39;)
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }    
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length    
    while (i--) {
      vm._watchers[i].teardown()
    }    
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }    
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)    
    // fire destroyed hook
    callHook(vm, &#39;destroyed&#39;)    
    // turn off all instance listeners.
    vm.$off()    
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }    
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
Copier après la connexion

$destroy这个原型函数中,执行了Vue的销毁操作,我们可以看到在执行销毁操作之前调用了callHook(vm, &#39;beforeDestroy&#39;),然后执行了一系列的销毁操作,包括删除掉所有的自身(self)、_watcher、数据引用等等,删除完成之后调用callHook(vm, &#39;destroyed&#39;)

截止到这里,整个Vue生命周期图示中的所有生命周期钩子都已经被执行完成了。那么剩下的activated、deactivated、errorCaptured这三个钩子函数是在何时被执行的呢?我们知道这三个函数都是针对于组件(component)的钩子函数。其中activated、deactivated这两个钩子函数分别是在keep-alive 组件激活和停用之后回调的,它们不牵扯到整个Vue的生命周期之中activated、deactivated这两个钩子函数的实现代码都在/src/core/instance/lifecycle.js下:

export function activateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = false
    if (isInInactiveTree(vm)) {      
    return
    }
  } else if (vm._directInactive) {    
  return
  }  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, &#39;activated&#39;)
  }
}

export function deactivateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = true
    if (isInInactiveTree(vm)) {      return
    }
  }  if (!vm._inactive) {
    vm._inactive = true
    for (let i = 0; i < vm.$children.length; i++) {
      deactivateChildComponent(vm.$children[i])
    }
    callHook(vm, &#39;deactivated&#39;)
  }
}
Copier après la connexion

而对于errorCaptured来说,它是在2.5.0之后新增的一个钩子函数,它的代码在/src/core/util/error.js中:

export function handleError (err: Error, vm: any, info: string) {
  if (vm) {    let cur = vm    while ((cur = cur.$parent)) {      
  const hooks = cur.$options.errorCaptured      
  if (hooks) {        
  for (let i = 0; i < hooks.length; i++) {          
  try {            
  const capture = hooks[i].call(cur, err, vm, info) === false
            if (capture) return
          } catch (e) {
            globalHandleError(e, cur, &#39;errorCaptured hook&#39;)
          }
        }
      }
      
    }
  }
  globalHandleError(err, vm, info)
}function globalHandleError (err, vm, info) {
  if (config.errorHandler) {    
  try {      
  return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      logError(e, null, &#39;config.errorHandler&#39;)
    }
  }
  logError(err, vm, info)
}function logError (err, vm, info) {
  if (process.env.NODE_ENV !== &#39;production&#39;) {
    warn(`Error in ${info}: "${err.toString()}"`, vm)
  }  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console !== &#39;undefined&#39;) {
    console.error(err)
  } else {    
  throw err
  }
}
Copier après la connexion

他是唯一一个没有通过callHook方法来执行的钩子函数,而是直接通过遍历cur(vm).$options.errorCaptured,来执行config.errorHandler.call(null, err, vm, info)的钩子函数。整个逻辑的结构与callHook使非常类似的。

截止到目前Vue中所有的生命周期钩子我们都已经介绍完成了,其中涉及到了一些源码的基础,是因为我觉得配合源码来一起看的话,会对整个Vue的运行过程有个更好的理解。大家一定要下载下来Vue的源代码,对照着我们的讲解来走一遍这个流程。

相关推荐:

vue生命周期、vue实例、模板语法

图概PHP生命周期,PHP生命周期

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal