Cet article présente principalement l'analyse du code source de la vue de contrôle des données de Vue. Il a une certaine valeur de référence. Maintenant, je le partage avec tout le monde. Les amis dans le besoin peuvent s'y référer
Analyse de la façon dont Vue est implémentée. vue des modifications et des mises à jour des données.
Préface
Il y a trois mois, j'ai lu le code source de vue pour analyser comment obtenir des données réactives. Le nom de l'article est le code source de vue pour réactif. data , et enfin analysé que la méthode update() de Watcher sera appelée après les modifications de données. Continuons donc à voir ce que fait update() après trois mois (au cours des trois derniers mois, j'ai utilisé React-Native pour construire. un projet, mais je n'ai pas l'intention de le faire. Résumé, car cela semble trop simple).
La méthode narrative de cet article est de suivre la logique de regarder le code source de la version I. vérifié est 2.5.2. J'ai forgé une copie du code source utilisé pour enregistrer les commentaires
Objectif
Effacer la direction de l'enquête pour atteindre le. Objectif.Parlons d'abord du comportement cible : quelle méthode est exécutée pour mettre à jour la vue après la modification des données. Ensuite, préparez-vous à commencer à chercher des réponses dans cette direction et commencez à partir de l'entrée du code source de la vue.
Démarrez. de la conclusion précédente
Revoyons d'abord la conclusion précédente :
Lorsque Vue est construite, un objet Observer sera créé sur les données (et quelques autres champs). sont interceptés. Le getter déclenche la collection de dépendances et le setter déclenche la notification.
Un autre L'objet est un Watcher Lors de l'enregistrement d'une montre, l'objet watch sera appelé une fois, ce qui déclenche le getter de la montre. et collecte les dépendances dans les dépôts du Watcher actuel Lorsqu'un setter du dep est déclenché, le Watcher actuel sera averti d'appeler la mise à jour de la méthode Watcher ().
Ensuite. nous commençons par enregistrer le Watcher lié au rendu.
Le fichier se trouve dans src/core/instance/lifecycle.js.
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
mountComponent
Le Watcher lié au rendu est appelé dans la méthode mountComponent(), cherchons donc où cette méthode est appelée. Il n'y a que 2 endroits, respectivement src/platforms/web/runtime/index.js et src/platforms. /weex/runtime/index.js, en prenant le web comme exemple :
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
Il s'avère que c'est la méthode $mount() que MountComponent() est appelée, (ou en spécifiant le champ el pendant vue la construction appellera automatiquement la méthode $mount()), car les objets rendus par web et weex (qu'est-ce que weex ? Je l'ai déjà présenté dans d'autres articles) sont différents, donc lors de la publication, différents fichiers doivent être introduits et finalement différents dists ne peut pas être publié (ce problème est laissé à l'étude de l'ensemble du processus de vue plus tard).
Ce qui suit est la méthode mountComponent :
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el // 放一份el到自己的属性里 if (!vm.$options.render) { // render应该经过处理了, 因为我们经常都是用template或者vue文件 // 判断是否存在render函数, 如果没有就把render函数写成空VNode来避免红错, 并报出黄错 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) { // 不看这里的代码了, 直接看else里的, 行为是一样的 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 // 注册一个Watcher 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 }
Ce code ne fait en fait que 3 choses :
Appelez le hook beforeMount
Create Watcher
Appelez le hook monté
(hahaha) Ensuite, le noyau est en fait d'établir le Watcher.
Regardez les paramètres du Watcher : vm c'est ça, updateComponent est une fonction, noop est vide, null est vide, et vrai signifie que c'est RenderWatcher
J'ai regardé isRenderWatcher dans Watcher :
if (isRenderWatcher) { vm._watcher = this }
Oui, je viens de faire une copie à utiliser pour le premier patch dans watcher Pour juger quelque chose ( d'après les commentaires, je ne sais toujours pas à quoi ça sert).
Le seul problème qui reste non résolu est ce qu'est updateComponent.
updateComponent
passe la fonction comme deuxième paramètre du constructeur de Watcher, alors cette fonction devient le getter de watcher. Si vous êtes intelligent, vous auriez dû deviner que dans ce updateComponent, ce n'est qu'en appelant les getters de toutes les données de la vue que vous pouvez le faire. vous établissez des dépendances dans l'observateur afin que la vue puisse répondre aux modifications de données.
updateComponent = () => { vm._update(vm._render(), hydrating) }
Ensuite, accédez à vm._update() et vm._render().
J'ai trouvé le ._render () dans src/core/instance/render.js.
Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options // todo: render和_parentVnode的由来 // reset _rendered flag on slots for duplicate slot check if (process.env.NODE_ENV !== 'production') { for (const key in vm.$slots) { // $flow-disable-line vm.$slots[key]._rendered = false } } if (_parentVnode) { vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject } // set parent vnode. this allows render functions to have access // to the data on the placeholder node. vm.$vnode = _parentVnode // render self let vnode try { vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { // catch其实不需要看了, 都是做异常处理, _vnode是在vm._update的时候保存的, 也就是上次的状态或是null(init的时候给的) handleError(e, vm, `render`) // return error render result, // or previous vnode to prevent render error causing blank component /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { if (vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) } catch (e) { handleError(e, vm, `renderError`) vnode = vm._vnode } } else { vnode = vm._vnode } } else { vnode = vm._vnode } } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode } }
Cette méthode fait :
Selon Utiliser la méthode de rendu de la machine virtuelle actuelle pour générer un VNode. (La méthode de rendu peut être compilée sur la base du modèle ou du fichier vue, on en déduit donc que l'écriture directe de la méthode de rendu est la plus efficace)
S'il y a un problème. avec la méthode render, puis appelez d'abord la méthode renderError, et si cela ne fonctionne pas, lisez le dernier vnode ou null.
S'il y a un nœud parent, mettez-le dans son propre attribut .parent.
Renvoie enfin VNode
Le noyau est donc cette phrase :
vnode = render.call(vm._renderProxy, vm.$createElement)
Le rendu (), vm._renderProxy, vm.$ Je ne sais même pas ce qu'est createElement
Regardons d'abord vm._renderProxy : il est défini lors de initMixin(). renvoie le proxy dans l'environnement de développement, nous pensons donc que c'est une vm qui peut être déboguée (c'est vm), nous verrons les détails plus tard
Le code de vm.$createElement est dans le vdom. Après l'avoir regardé, c'est une méthode qui renvoie un VNode.
le rendu est un peu compliqué, mais il ne peut pas être étudié plus tard. En bref, le fichier unique du modèle ou de la vue et la cible de montage sont analysés dans le rendu. function.
Petit résumé : La valeur de retour de vm._render() est VNode, selon la fonction de rendu de la vm actuelle
Ensuite, regardons vm._update()
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this if (vm._isMounted) { callHook(vm, 'beforeUpdate') } // 记录update之前的状态 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) { // 初次加载, 只有_update方法更新vm._vnode, 初始化是null // initial render vm.$el = vm.__patch__( // patch创建新dom 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) // patch更新dom } 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. }
La partie qui nous intéresse est en fait la partie __patch(). __patch() effectue l'opération sur le dom, et elle est jugée dans _update(). Si c'est le cas, créez-en un nouveau ? dom. Sinon, transmettez l'ancien et le nouveau nœuds pour comparaison, puis opérez
Conclusion
Le rendu de vue de Vue est un type spécial de Watcher. Le contenu de la montre est une fonction. La fonction de rendu est appelée pendant le processus d'exécution de la fonction. Le rendu est compilé à partir du modèle ou du dom d'el (le modèle en contient). objets observés). Par conséquent, les modifications dans les données observées dans le modèle déclenchent la méthode update() de Watcher pour restituer la vue
Legacy
.
Où la fonction de rendu est-elle compilée ?
Quel est le processus d'introduction des différentes plates-formes lorsque le code source de vue est publié et enfin de sa saisie dans dist ?
Analyse de __patch__ et de VNode
Ce qui précède est l'intégralité du contenu de cet article. J'espère qu'il vous sera utile. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois !
Recommandations associées :
À propos des idées de Vue pour résoudre les conflits de routage entre domaines
À propos de la définition dynamique des paramètres de routage par Vue Introduction
À propos de l'analyse du code source du patch Vue Virtual Dom
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!