Home > Article > Web Front-end > Analysis of the source code of vue data control view
This article mainly introduces the analysis of the vue data control view source code. It has a certain reference value. Now I share it with you. Friends in need can refer to it.
Analysis of how vue implements data changes and updates view.
Preface
Three months ago I read the vue source code to analyze how to achieve responsive data. The article is called vue source code for responsive data. , and finally analyzed that the update() method of Watcher will be called after the data changes. So let’s continue to see what update() does after three months. (In the past three months, I have used react-native to build a project, but I have no intention of doing so. Summarized, because it seems too simple).
The narrative method of this article is to follow the logic of looking at the source code. The version of vue I checked is 2.5.2. I forked a copy of the source code. Used to record comments.
Purpose
Only by clarifying the direction of investigation can we reach the goal. Let’s talk about the target behavior first: what method is executed to update the view after the data changes. Then prepare to start looking for answers in this direction and start from the entrance of vue source code.
Start from the previous conclusion
Let’s review the previous conclusion first:
When Vue is constructed, an Observer object will be created on the data (and some other fields). The getter and setter are intercepted. The getter triggers dependency collection and the setter triggers notify.
Another The object is Watcher. When registering a watch, the watch object will be called once, which triggers the getter of the watch object and collects the dependencies into the deps of the current Watcher. When any setter of the dep is triggered, the current Watcher will be notified to call the update of the Watcher. () method.
Then here we start by registering the rendering-related Watcher.
The file is found in src/core/instance/lifecycle.js.
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
mountComponent
The rendering-related Watcher is called in the mountComponent() method, so let’s search where this method is called. There are only 2 places, respectively. They are src/platforms/web/runtime/index.js and src/platforms/weex/runtime/index.js. Take web as an example:
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
It turns out that it is the $mount() method that calls mountComponent( ), (or specifying the el field during vue construction will automatically call the $mount() method), because the rendering objects of web and weex (what is weex? I have introduced it in other articles before) are different, so when publishing, it should be After introducing different files, different dists cannot be sent (this problem is left to study the entire process of vue later).
The following is the mountComponent method:
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 }
This code actually only does 3 things:
Call the beforeMount hook
Create Watcher
Call the mounted hook
(Hahaha) So actually the core is to create a Watcher.
Look at the parameters of the Watcher: vm is this, updateComponent is a function, noop is empty, null is empty , true means RenderWatcher.
Looked at isRenderWatcher in Watcher:
if (isRenderWatcher) { vm._watcher = this }
Yes, I just made a copy to judge something when the watcher patches for the first time (from the comments I read it in, I still don’t know what it is for).
The only problem that has not been solved is what updateComponent is.
updateComponent
If the function is passed as the second parameter of the Watcher constructor, then this function becomes the getter of the watcher. If you are smart, you should have guessed that all the data in the view must be called in this updateComponent. Getter can establish dependencies in the watcher so that the view can respond to data changes.
updateComponent = () => { vm._update(vm._render(), hydrating) }
Then go to vm._update() and vm._render().
In src/core/ instance/render.js found the ._render() method.
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 } }
This method does:
Generate VNode based on the render method of the current vm. (render method It may be compiled based on the template or vue file, so it is inferred that writing the render method directly is the most efficient)
If there is a problem with the render method, then call the renderError method first, and if it fails, read it. The vnode of this time is either null.
If there is a parent node, put it in its own .parent attribute.
Finally return VNode
So the core is this sentence:
vnode = render.call(vm._renderProxy, vm.$createElement)
I don’t know what render(), vm._renderProxy, vm.$createElement are.
First Look at vm._renderProxy: it is set during initMixin(). It returns vm in the production environment and returns the proxy in the development environment. Then we think it is a vm that can be debugged (that is, vm). We will look at the details later.
## The code of #vm.$createElement is in the vdom folder. After looking at it, it is a method that returns a VNode. render is a bit complicated. Is it possible to study it later? In short, it is to combine the template or vue single file with mount The target parse becomes the render function.Small summary: The return value of vm._render() is VNode, based on the render function of the current vmLet’s look at 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. }The part we care about is actually the __patch() part. __patch() operates on the dom. In _update(), it is judged whether it is the first call. If so, create a new dom. If not, pass it in. Compare the old and new nodes before operating.
Conclusion
Vue's view rendering is a special kind of Watcher. The content of the watch is a function. The render function is called during the running process of the function. The render is compiled by the template or el's dom (the template contains some observed objects). data). Therefore, changes in the observed data in the template trigger the Watcher's update() method to re-render the view.
Legacy
Where is the render function compiled?
What is the process of introducing different platforms when vue source code is released and finally typing it into dist
Analysis of __patch__ and VNode
The above is the entire content of this article. I hope it will be useful to you. Everyone’s learning is helpful. For more related content, please pay attention to the PHP Chinese website!
Related recommendations:
About Vue’s ideas for solving cross-domain routing conflicts
About Vue’s dynamic setting of routing parameters Introduction
#About vue virtual dom patch source code analysis
The above is the detailed content of Analysis of the source code of vue data control view. For more information, please follow other related articles on the PHP Chinese website!