Heim >Web-Frontend >js-Tutorial >Vues Lebenszyklus und Quellcode-Implementierung (Code)
Der Inhalt dieses Artikels befasst sich mit dem Lebenszyklus und der Quellcode-Implementierung (Code) von Vue. Ich hoffe, dass er für Freunde hilfreich ist.
Durch das Lernen haben wir die gesamte grundlegende Syntax von Vue gelernt, einschließlich:
1, {{Mustache}}-Syntax
2. v-if, v-else, v-else-if, v-show
3. v-für
4. v-bind
5. V-Modell
6. v-on
Wenn Sie diese Grammatiken bereits im Kopf haben, lesen Sie bitte weiter. Wenn Sie diese Grammatiken nicht sehr gut beherrschen, werden Sie sich hoffentlich die vorherigen ansehen.
In diesem Kapitel untersuchen wir Vues Lebenszyklus. Werfen wir zunächst einen Blick auf die Definition von Vues Lebenszyklus.
Jede Vue-Instanz durchläuft beim Erstellen eine Reihe von Initialisierungsprozessen. Sie müssen beispielsweise die Datenüberwachung einrichten, Vorlagen kompilieren, die Instanz im DOM bereitstellen und das DOM aktualisieren, wenn sich die Daten ändern , usw. Gleichzeitig werden während dieses Prozesses auch einige Funktionen namens Lebenszyklus-Hooks ausgeführt, die Benutzern die Möglichkeit geben, in verschiedenen Phasen ihren eigenen Code hinzuzufügen.
Dies sind die Beschreibungsinformationen, die auf der offiziellen Website von Vue bereitgestellt werden: Während des Prozesses von Vue von der Erstellung einer Instanz bis zur endgültigen Zerstörung wird eine Reihe entsprechender Methoden ausgeführt Nach dem aktuellen Vue-Status nennen wir diese Methoden: Lebenszyklus-Hooks . Schauen wir uns das Lebenszyklusdiagramm unten an:
Im obigen Diagramm werden insgesamt 8 Lebenszyklus-Hook-Funktionen angezeigt Diese Funktion beschreibt den gesamten Laufzyklus von Vue. Derzeit ist die Vue-Version -2.5.16. Vue verfügt über insgesamt 11 Lebenszyklus-Hooks. Zusätzlich zu den bisherigen 8 gibt es auch 3 Lebenszyklus-Hooks für die -Komponente . Werfen wir einen Blick auf alle Hook-Funktionserklärungen und anhand des obigen Diagramms können wir den Vue-Laufzyklus besser verstehen.
1. beforeCreate: Wird nach der Instanzinitialisierung und vor der Konfiguration des Datenbeobachters und des Ereignisses/Beobachterereignisses aufgerufen.
2. erstellt: Wird unmittelbar nach der Erstellung der Instanz aufgerufen. In diesem Schritt hat die Instanz die folgende Konfiguration abgeschlossen: Datenbeobachter, Vorgänge für Eigenschaften und Methoden sowie Rückrufe von Überwachungs-/Ereignisereignissen. Allerdings hat die Montagephase noch nicht begonnen und das Attribut $el
ist derzeit nicht sichtbar.
3. beforeMount: Wird vor dem Mounten aufgerufen: Die zugehörige Renderfunktion wird zum ersten Mal aufgerufen.
4. mounted: el wird durch das neu erstellte vm.$el
ersetzt und der Hook wird aufgerufen, nachdem er in der Instanz gemountet wurde. Wenn die Root-Instanz ein Element im Dokument mountet, befindet sich vm.$el
auch im Dokument, wenn „mounting“ aufgerufen wird (PS: Beachten Sie, dass „mounted“ nicht verspricht, dass alle untergeordneten Komponenten auch zusammen gemountet werden. Wenn Sie warten möchten, bis die gesamte Ansicht ist fertig gerendert, Sie können mounted durch vm.$nextTick
ersetzen :). vm.$nextTick
Es wird in den folgenden Kapiteln ausführlich erklärt. Jeder muss diese Sache hier wissen.
5. beforeUpdate: Wird aufgerufen, wenn die Daten aktualisiert werden, was vor dem Patchen des virtuellen DOM geschieht. Dies eignet sich für den Zugriff auf das vorhandene DOM vor der Aktualisierung, beispielsweise zum manuellen Entfernen eines hinzugefügten Ereignis-Listeners.
6. aktualisiert: Dieser Hook wird aufgerufen, nachdem das virtuelle DOM aufgrund von Datenänderungen neu gerendert und gepatcht wurde. Wenn dieser Hook aufgerufen wird, wurde das Komponenten-DOM aktualisiert, sodass Sie jetzt Vorgänge ausführen können, die vom DOM abhängen. In den meisten Fällen sollten Sie jedoch einen Zustandswechsel in diesem Zeitraum vermeiden. Wenn Sie auf Zustandsänderungen reagieren möchten, ist es normalerweise am besten, stattdessen berechnete Eigenschaften oder Watcher zu verwenden (PS: Berechnete Eigenschaften und Watcher werden in späteren Kapiteln vorgestellt).
7. aktiviert: Wird aufgerufen, wenn die Keep-Alive-Komponente aktiviert ist (PS: Bezogen auf die Komponente wird Ihnen Keep-Alive bei der Erläuterung der Komponente vorgestellt).
8. deaktiviert: Wird aufgerufen, wenn die Keep-Alive-Komponente deaktiviert ist (PS: Bezogen auf die Komponente wird Ihnen Keep-Alive bei der Erläuterung der Komponente vorgestellt).
9. beforeDestroy: Wird aufgerufen, bevor die Instanz zerstört wird. Zu diesem Zeitpunkt ist die Instanz noch vollständig verfügbar.
10. zerstört: Wird aufgerufen, nachdem die Vue-Instanz zerstört wurde. Beim Aufruf wird alles, auf das die Vue-Instanz verweist, entbunden, alle Ereignis-Listener werden entfernt und alle untergeordneten Instanzen werden zerstört.
11. errorCaptured (neu in 2.5.0+): Wird aufgerufen, wenn ein Fehler von einer Nachkommenkomponente erfasst wird. Dieser Hook empfängt drei Parameter: das Fehlerobjekt, die Komponenteninstanz, in der der Fehler aufgetreten ist, und eine Zeichenfolge mit Informationen über die Fehlerquelle. Dieser Hook kann false zurückgeben, um zu verhindern, dass sich der Fehler weiter nach oben ausbreitet.
Dies sind alle Lebenszyklus-Hooks in Vue (2.5.16), um es für alle verständlicher zu machen, werfen wir einen Blick auf aus bis Zerstörung umgesetzt wird. Sie können hier klicken, um den neuesten Code von Vue herunterzuladen.
Werfen wir zunächst einen kurzen Blick daraufVue源代码的基础结构
.
. ├── BACKERS.md ├── LICENSE ├── README.md├── benchmarks ├── dist ├── examples ├── flow ├── node_modules ├── package.json├── packages ├── scripts ├── src ├── test ├── types └── yarn.lock
Dies ist das Verzeichnis der ersten Ebene nach dem Herunterladen des Codes, dist文件夹下为Vue编译之后的代码,我们平时引入的Vue.js文件都在这里
, Vue使用了flow作为JavaScript静态类型检查工具,相关的代码都在flow文件夹下面
, scripts文件夹下面是代码构建的相关配置,Vue主要使用Rollup进行的代码构建
, src文件夹下面就是所有Vue的源代码
. Wir werden hier nicht zu viele andere Inhalte beschreiben, sondern uns auf unser Thema konzentrieren: Wie der Lebenszykluscode von Vue implementiert wird, werfen wir einen Blick auf den Ordner src
.
. ├── compiler :Vue编译相关 ├── core :Vue的核心代码 ├── platforms :web/weex平台支持,入口文件 ├── server :服务端 ├── sfc :解析.vue文件 └── shared :公共代码
Dies ist die Verzeichnisstruktur unter unserem Ordner src
, und der Ort, an dem unser Vue generiert wird, befindet sich in /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) }
Wir können sehen: Vue ist eine Methode, ein Konstruktor, der mit Function implementiert wird, sodass wir Instanzen von Vue nur über new erstellen können. Initialisieren Sie dann Vue mit der Vue实例
-Methode von _init
. _init
ist ein prototype
, das von Vue bis 原型属性
implementiert wird. Werfen wir einen Blick auf seine _init
Methodenimplementierung.
Unter dem Ordner /src/core/instance/init.js
implementiert Vue die _init
-Methode
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) } }
Ich schaue mir hauptsächlich den Code an, der sich auf seinen Lebenszyklus bezieht. Wir können sehen, dass Vue先调用了initLifecycle(vm)、initEvents(vm)、initRender(vm)
diese drei Methoden verwendet werden um 生命周期、事件、渲染函数
zu initialisieren. Diese Prozesse finden innerhalb von Vue初始化的过程(_init方法)中
und vor dem Aufruf von beforeCreate钩子
statt.
Dann ruft Vue callHook (vm: Component, hook: string)
über die Methode 钩子函数(hook)
auf, die vm(Vue实例对象),hook(钩子函数名称)
empfängt, um 生命周期函数
auszuführen. In Vue werden fast alle Hook-Funktionen (außer ) in Vue über errorCaptured
aufgerufen. Schauen wir uns den Code von callHook (vm: Component, hook: string)
unter an: callHook
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('hook:' + hook) } popTarget() }
/src/core/instance/lifecycle.js
Seine Logik ist ebenfalls sehr einfach. Entsprechend dem eingehenden wird das entsprechende Callback-Funktionsarray von der Instanz abgerufen (. unter unter 🎜>) und führen Sie es dann bequem aus. hook
/packages/vue-template-compiler/browser.js
ruft dann LIFECYCLE_HOOKS
nach der Initialisierung auf. Zu diesem Zeitpunkt: Wir haben keine Möglichkeit, 生命周期、事件、渲染函数
und andere Daten beforeCreate钩子
abzurufen. data、props
Nach dem Aufruf von werden diese drei Methoden zum Initialisieren von
aufgerufen beforeCreate钩子
und andere Daten bereits erhalten, aber Vue ist noch nicht gestartet Vue调用了initInjections(vm)、initState(vm)、initProvide(vm)
, daher können wir noch nicht auf das DOM zugreifen (PS: Wir können über data、props、watcher
darauf zugreifen, was wir in den folgenden Kapiteln ausführlich erläutern werden ). created钩子函数
data、props
Nach dem Aufruf von 渲染DOM
beginnt vm.$nextTick
Vue mit dem Mounten des DOM und führt
Methoden kommen und gehen. created钩子
Die Deklaration der Prototypmethode befindet sich in . Schauen wir uns die Implementierung dieses Codes an:
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 !== 'production' && 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 === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !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 !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } 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 !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } 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('p') container.appendChild(el.cloneNode(true)) return container.innerHTML } }
vm.$mount(vm.$options.el)
Die Hauptfunktion dieses Teils des Codes: Vue.prototype.$mount
besteht darin, Vue.prototype.$mount
/src/platforms/web/entry-runtime-with-compiler.js
. Wie aus dem obigen Code ersichtlich ist, darf el nicht auf Root-Tags wie und gemountet werden. template模板的解析
Bestimmen Sie dann, ob vorhanden ist, und bestimmen Sie dann, ob vorhanden ist. Die Vorlage kann body
html
, render函数 -> if (!options.render) {...}
sein. Wenn nicht, analysieren Sie template
als . Aus dem obigen Code ist ersichtlich, dass string类型的id
unabhängig davon, ob wir DOM节点
verwenden oder übergeben, schließlich die gesamte Vorlage el
in Form der Funktion template
analysiert. 单文件组件(.Vue)
Aus unserem Diagramm können wir ersehen, dass nach Abschluss der Vorlagenanalyse el、template属性
aufgerufen wird. Wo heißt dieses render
? Schauen wir nach unten. Die Prototyp-Methode hat ein wiederverwendbares Design. Unter
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
这是一个公共的挂载方法,目的是为了被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 !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && 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's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component'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, 'mounted') } return vm }
由上面的代码可以看出在执行vm._render()
之前,调用了callHook(vm, 'beforeMount')
,这个时候相关的 render 函数首次被调用,调用完成之后,执行了callHook(vm, 'mounted')
方法,标记着el 被新创建的 vm.$el 替换,并被挂载到实例上。
然后就进入了我们页面正常交互的时间
,也就是beforeUpdate
和updated
这两个回调钩子的执行时机。这两个钩子函数是在数据更新的时候进行回调的函数,Vue在/src/core/instance/lifecycle.js
文件下有一个_update
的原型声明:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this if (vm._isMounted) { callHook(vm, 'beforeUpdate') } 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's updated hook. }
我们可以看到在如果_isMounted
为ture
的话(DOM已经被挂载)则会调用callHook(vm, 'beforeUpdate')
方法,然后会对虚拟DOM进行重新渲染。然后在/src/core/observer/scheduler.js
下的flushSchedulerQueue()
函数中渲染DOM,在渲染完成调用callHook(vm, 'updated')
,代码如下:。
/** * 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, 'updated') } } }
当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, 'beforeDestroy') 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, 'destroyed') // 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 } }
在$destroy
这个原型函数中,执行了Vue的销毁操作
,我们可以看到在执行销毁操作之前调用了callHook(vm, 'beforeDestroy')
,然后执行了一系列的销毁操作,包括删除掉所有的自身(self)、_watcher、数据引用
等等,删除完成之后调用callHook(vm, 'destroyed')
。
截止到这里,整个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, 'activated') } } 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, 'deactivated') } }
而对于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, 'errorCaptured hook') } } } } } 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, 'config.errorHandler') } } logError(err, vm, info) }function logError (err, vm, info) { if (process.env.NODE_ENV !== 'production') { warn(`Error in ${info}: "${err.toString()}"`, vm) } /* istanbul ignore else */ if ((inBrowser || inWeex) && typeof console !== 'undefined') { console.error(err) } else { throw err } }
他是唯一一个没有通过callHook
方法来执行的钩子函数,而是直接通过遍历cur(vm).$options.errorCaptured
,来执行config.errorHandler.call(null, err, vm, info)
的钩子函数。整个逻辑的结构与callHook
使非常类似的。
截止到目前Vue中所有的生命周期钩子我们都已经介绍完成了,其中涉及到了一些源码的基础,是因为我觉得配合源码来一起看的话,会对整个Vue的运行过程有个更好的理解。大家一定要下载下来Vue的源代码,对照着我们的讲解来走一遍这个流程。
相关推荐:
Das obige ist der detaillierte Inhalt vonVues Lebenszyklus und Quellcode-Implementierung (Code). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!