上一节我们提到, VNode 在创建完成后, 会通过 _update 方法将虚拟 dom 通过 dom 操作, append,insert 等等插入到真实 dom 树上, 我们先来看_update
- vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
- const vm: Component = this
- const prevEl = vm.$el
- const prevVnode = vm._vnode
- const restoreActiveInstance = setActiveInstance(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 */)
- } else {
- // updates
- vm.$el = vm.__patch__(prevVnode, vnode)
- }
- restoreActiveInstance()
- // 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.
- }
_update 方法是在我们执行_init 方法时执行一些列初始化操作时定义的, update 方法会在两种情况下被调用, 一是 new Vue 初始化的时候, 还有一种情况就是当我们改变 data 数据, 页面重新渲染时, 然后调用 patch 方法, 我们进入 patch
export const patch: Function = createPatchFunction({ nodeOps, modules })
可以看到 patch 方法是由一 createPathFunction 方法返回的, 这个方法传入了两个参数, nodeOps,modules, 摘取一部分 nodeOps 内容
- export function createElementNS (namespace: string, tagName: string): Element {
- return document.createElementNS(namespaceMap[namespace], tagName)
- }
- export function createTextNode (text: string): Text {
- return document.createTextNode(text)
- }
- export function createComment (text: string): Comment {
- return document.createComment(text)
- }
可以看到, 里面都是一些原生 dom 操作的封装, 摘取 modules 一部分内容
- export function addClass (el: htmlElement, cls: ?string) {
- /* istanbul ignore if */
- if (!cls || !(cls = cls.trim())) {
- return
- }
- /* istanbul ignore else */
- if (el.classList) {
- if (cls.indexOf(' ')> -1) {
- cls.split(whitespaceRE).forEach(c => el.classList.add(c))
- } else {
- el.classList.add(cls)
- }
- } else {
- const cur = ` ${el.getAttribute('class') || ''} `
- if (cur.indexOf('' + cls +' ') <0) {
- el.setAttribute('class', (cur + cls).trim())
- }
- }
- }
可以看到, 是一些对原生 dom 特性控制的封装,, 以及一些辅助函数,
下面我们回到 createPatchFunction,createPatchFunction 方法中首先定义了好多的喜函数, 最后返回了一个函数, 即 patch, 我么来看下这个 patch
- return function patch (oldVnode, vnode, hydrating, removeOnly) {
- if (isUndef(vnode)) {
- if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
- return
- }
- let isInitialPatch = false
- const insertedVnodeQueue = []
- if (isUndef(oldVnode)) {
- // empty mount (likely as component), create new root element
- isInitialPatch = true
- createElm(vnode, insertedVnodeQueue)
- } else {
- const isRealElement = isDef(oldVnode.nodeType)
- if (!isRealElement && sameVnode(oldVnode, vnode)) {
- // patch existing root node
- patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
- } else {
- if (isRealElement) {
- // mounting to a real element
- // check if this is server-rendered content and if we can perform
- // a successful hydration.
- if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
- oldVnode.removeAttribute(SSR_ATTR)
- hydrating = true
- }
- if (isTrue(hydrating)) {
- if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
- invokeInsertHook(vnode, insertedVnodeQueue, true)
- return oldVnode
- } else if (process.env.NODE_ENV !== 'production') {
- warn(
- 'The client-side rendered virtual DOM tree is not matching' +
- 'server-rendered content. This is likely caused by incorrect' +
- 'HTML markup, for example nesting block-level elements inside' +
- '<p>, or missing <tbody>. Bailing hydration and performing' +
- 'full client-side render.'
- )
- }
- }
- // either not server-rendered, or hydration failed.
- // create an empty node and replace it
- oldVnode = emptyNodeAt(oldVnode)
- }
- // replacing existing element
- const oldElm = oldVnode.elm
- const parentElm = nodeOps.parentNode(oldElm)
- // create new node
- createElm(
- vnode,
- insertedVnodeQueue,
- // extremely rare edge case: do not insert if old element is in a
- // leaving transition. Only happens when combining transition +
- // keep-alive + HOCs. (#4590)
- oldElm._leaveCb ? null : parentElm,
- nodeOps.nextSibling(oldElm)
- )
- // update parent placeholder node element, recursively
- if (isDef(vnode.parent)) {
- let ancestor = vnode.parent
- const patchable = isPatchable(vnode)
- while (ancestor) {
- for (let i = 0; i <cbs.destroy.length; ++i) {
- cbs.destroy[i](ancestor)
- }
- ancestor.elm = vnode.elm
- if (patchable) {
- for (let i = 0; i < cbs.create.length; ++i) {
- cbs.create[i](emptyNode, ancestor)
- }
- // #6513
- // invoke insert hooks that may have been merged by create hooks.
- // e.g. for directives that uses the "inserted" hook.
- const insert = ancestor.data.hook.insert
- if (insert.merged) {
- // start at index 1 to avoid re-invoking component mounted hook
- for (let i = 1; i < insert.fns.length; i++) {
- insert.fns[i]()
- }
- }
- } else {
- registerRef(ancestor)
- }
- ancestor = ancestor.parent
- }
- }
- // destroy old node
- if (isDef(parentElm)) {
- removeVnodes(parentElm, [oldVnode], 0, 0)
- } else if (isDef(oldVnode.tag)) {
- invokeDestroyHook(oldVnode)
- }
- }
- }
- invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
- return vnode.elm
- }
重点, 执行了 createElm 方法, 这个方法定义于传入的 modules 辅助函数中, 这个方法才是真实 dom 操作的核心所在, 我们进入 createElm
- function createElm (
- vnode,
- insertedVnodeQueue,
- parentElm,
- refElm,
- nested,
- ownerArray,
- index
- ) {
- if (isDef(vnode.elm) && isDef(ownerArray)) {
- // This vnode was used in a previous render!
- // now it's used as a new node, overwriting its elm would cause
- // potential patch errors down the road when it's used as an insertion
- // reference node. Instead, we clone the node on-demand before creating
- // associated DOM element for it.
- vnode = ownerArray[index] = cloneVNode(vnode)
- }
- vnode.isRootInsert = !nested // for transition enter check
- if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
- return
- }
- const data = vnode.data
- const children = vnode.children
- const tag = vnode.tag
- if (isDef(tag)) {
- if (process.env.NODE_ENV !== 'production') {
- if (data && data.pre) {
- creatingElmInVPre++
- }
- if (isUnknownElement(vnode, creatingElmInVPre)) {
- warn(
- 'Unknown custom element: <' + tag + '> - did you' +
- 'register the component correctly? For recursive components,' +
- 'make sure to provide the"name"option.',
- vnode.context
- )
- }
- }
- vnode.elm = vnode.ns
- ? nodeOps.createElementNS(vnode.ns, tag)
- : nodeOps.createElement(tag, vnode)
- setScope(vnode)
- /* istanbul ignore if */
- if (__WEEX__) {
- // in Weex, the default insertion order is parent-first.
- // List items can be optimized to use children-first insertion
- // with append="tree".
- const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
- if (!appendAsTree) {
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue)
- }
- insert(parentElm, vnode.elm, refElm)
- }
- createChildren(vnode, children, insertedVnodeQueue)
- if (appendAsTree) {
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue)
- }
- insert(parentElm, vnode.elm, refElm)
- }
- } else {
- createChildren(vnode, children, insertedVnodeQueue)
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue)
- }
- insert(parentElm, vnode.elm, refElm)
- }
- if (process.env.NODE_ENV !== 'production' && data && data.pre) {
- creatingElmInVPre--
- }
- } else if (isTrue(vnode.isComment)) {
- vnode.elm = nodeOps.createComment(vnode.text)
- insert(parentElm, vnode.elm, refElm)
- } else {
- vnode.elm = nodeOps.createTextNode(vnode.text)
- insert(parentElm, vnode.elm, refElm)
- }
- }
这个函数名字我们可以大胆猜想, 作者把这一部映射的操作且分成了很多逻辑, 函数有点多, 名字不太好起了, createElm,,,
可以看到这里大量用了原生的 API, 来创建 dom 元素, createChildren , 对 VNode 的子元素进行递归调用传递父元素, 来构建真实的 dom 树, 最后, 通过调用 insert 方法, 即 dom 的 insert 方法, 将整个 dom 树一次性插入到 dom 树中, 大家可以通过打断点的方法, 完整查看整个_update 流程
来源: http://www.jianshu.com/p/e0b88a8ee620