vue2從數(shù)據(jù)到視圖渲染之模板渲染詳解
引言
一般使用html,css和javascript直接將數(shù)據(jù)渲染到視圖中,需要關(guān)心如何去操作dom,如果數(shù)據(jù)量比較大,那么操作過(guò)程將會(huì)繁瑣且不好控制。vue將這些操作內(nèi)置,開(kāi)發(fā)過(guò)程中只需要關(guān)注數(shù)據(jù)的變化,視圖的變化由vue框架內(nèi)部實(shí)現(xiàn)。
將數(shù)據(jù)進(jìn)行視圖渲染的一個(gè)簡(jiǎn)單例子:
// html <div id="app"> {{ message }} </div>
// js var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' }, })
得到視圖:
Hello Vue!
1、從new Vue()入口開(kāi)始:
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) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
在實(shí)例化Vue的過(guò)程中,首先執(zhí)行this._init方法;
2、this._init
this._init方法是在initMixin(Vue)時(shí)混入的
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // 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') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } }
其中進(jìn)行生命周期、事件、render、數(shù)據(jù)等功能的初始化,最后執(zhí)行了
vm.mount(vm.mount(vm.mount(vm.options.el)方法;
3、掛載函數(shù)vm.$mount(vm.$options.el)
該函數(shù)在entry-runtime-with-compiler.js函數(shù)中有定義
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, { outputSourceRange: process.env.NODE_ENV !== 'production', 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) } function getOuterHTML (el: Element): string { if (el.outerHTML) { return el.outerHTML } else { const container = document.createElement('div') container.appendChild(el.cloneNode(true)) return container.innerHTML } }
在沒(méi)有render的條件下,當(dāng)前存在template.nodeType,則通過(guò)template.innerHTML獲得模板template,再通過(guò)compileToFunctions編譯生成render函數(shù)。 運(yùn)行結(jié)果如下:
function f() { with (this) { return _c("div", { attrs: { id: "app" } }, [_v(_s(message))]); } }
獲得render函數(shù)后,再執(zhí)行緩存的mount,該方法在platforms/web/runtime/index.js中
Vue.prototype.$mount = function ( el, hydrating ) { el = el && inBrowser ? query(el) : undefined; return mountComponent(this, el, hydrating) };
4、mountComponent函數(shù)
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, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, 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 }
在當(dāng)前函數(shù)中定義了updateComponent
函數(shù),然后,通過(guò)new Watcher進(jìn)行實(shí)例化將updateComponent
作為參數(shù)傳入,在實(shí)例化的最后一步會(huì)執(zhí)行回調(diào)函數(shù)然后執(zhí)行updateComponent
,即執(zhí)行vm._update(vm._render(), hydrating)
。
5、_render函數(shù)
該方法是在renderMixin(Vue)的時(shí)候混入的
Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ) } // 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 { // There's no need to maintain a stack because all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { 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' && 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 } } finally { currentRenderingInstance = null } // if the returned array contains only a single node, allow it if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0] } // 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 }
其中vnode = render.call(vm._renderProxy, vm.$createElement)
函數(shù)以vm.$createElement
為參數(shù)執(zhí)行render函數(shù)。以上例子是通過(guò)vue提供的編譯方式編譯生成的render函數(shù):
function f() { with (this) { return _c("div", { attrs: { id: "app" } }, [_v(_s(message))]); } }
也可以通過(guò)以下手寫(xiě)render的方式模擬
render: function (createElement) { return createElement('div', { attrs: { id: 'app' }, }, this.message) },
可以看出,在編譯生成的例子中用到了_c
用來(lái)生成虛擬DOM,在手寫(xiě)render的例子中用vm.$createElement
生成虛擬DOM。_c
和vm.$createElement
都是在執(zhí)行this._init的時(shí)候執(zhí)行的initRender(vm)的時(shí)候掛載的。如下:
export function initRender (vm: Component) { vm._vnode = null // the root of the child tree vm._staticTrees = null // v-once cached trees const options = vm.$options const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree const renderContext = parentVnode && parentVnode.context vm.$slots = resolveSlots(options._renderChildren, renderContext) vm.$scopedSlots = emptyObject // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // normalization is always applied for the public version, used in // user-written render functions. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated const parentData = parentVnode && parentVnode.data /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => { !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) }, true) defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => { !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) }, true) } else { defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true) defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true) } }
其中都是調(diào)用了createElement方法,僅僅是最后一位參數(shù)不相同。
6、createElement函數(shù)返回虛擬DOM
// wrapper function for providing a more flexible interface // without getting yelled at by flow export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> { if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } return _createElement(context, tag, data, children, normalizationType) }
是個(gè)包裝函數(shù),為了提供一個(gè)更加靈活的參數(shù)傳入方式,最后執(zhí)行_createElement
函數(shù)
export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { if (isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV !== 'production' && warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 'Always create fresh vnode data objects in each render!', context ) return createEmptyVNode() } // object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { // in case of component :is set to falsy value return createEmptyVNode() } // warn against non-primitive key if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { if (!__WEEX__ || !('@binding' in data.key)) { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.', context ) } } // support single function children as default scoped slot if (Array.isArray(children) && typeof children[0] === 'function' ) { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns if (typeof tag === 'string') { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { // platform built-in elements if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) { warn( `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`, context ) } vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // component vnode = createComponent(Ctor, data, context, children, tag) } else { // unknown or unlisted namespaced elements // check at runtime because it may get assigned a namespace when its // parent normalizes children vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { // direct component options / constructor vnode = createComponent(tag, data, context, children) } if (Array.isArray(vnode)) { return vnode } else if (isDef(vnode)) { if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else { return createEmptyVNode() } }
在當(dāng)前例子中,因?yàn)閠ab為'div',最后會(huì)執(zhí)行到
vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context )
其中vnode是Vnode的實(shí)例,是用來(lái)描述當(dāng)前節(jié)點(diǎn)的重要屬性。
7、_update函數(shù)
該方法是在lifecycleMixin(Vue)的時(shí)候混入的
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. }
如果有舊的虛擬dom,會(huì)執(zhí)行vm.$el = vm.__patch__(prevVnode, vnode)
,這種場(chǎng)景是在數(shù)據(jù)發(fā)生變化的時(shí)候發(fā)生;
當(dāng)前例子是首次渲染,所以會(huì)執(zhí)行vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
,其中_patch函數(shù)在platforms/web/runtime/index.js中Vue.prototype.__patch__ = inBrowser ? patch : noop
。
8、patch函數(shù)
該方法在platforms/web/runtime/patch.js中
import * as nodeOps from 'web/runtime/node-ops' import { createPatchFunction } from 'core/vdom/patch' import baseModules from 'core/vdom/modules/index' import platformModules from 'web/runtime/modules/index' // the directive module should be applied last, after all // built-in modules have been applied. const modules = platformModules.concat(baseModules) export const patch: Function = createPatchFunction({ nodeOps, modules })
將獲取到nodeOps原生節(jié)點(diǎn)操作方法合集和模塊操作方法作為createPatchFunction
的參數(shù)傳入,該方法在core/vdom/patch.js中
export function createPatchFunction (backend) { let i, j const cbs = {} const { modules, nodeOps } = backend for (i = 0; i < hooks.length; ++i) { cbs[hooks[i]] = [] for (j = 0; j < modules.length; ++j) { if (isDef(modules[j][hooks[i]])) { cbs[hooks[i]].push(modules[j][hooks[i]]) } } } // 各種方法 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([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm } }
在createPatchFunction
方法中,首先將modules
在hook
數(shù)組存在的方法合并成cbs對(duì)象,然后又定義了各種渲染需要的方法,最后,通過(guò)return function patch (oldVnode, vnode, hydrating, removeOnly)
返回真正需要執(zhí)行的patch函數(shù)。其實(shí)在vue實(shí)例化執(zhí)行過(guò)程中,只會(huì)進(jìn)行一次Vue.prototype.__patch__ = inBrowser ? patch : noop
的求值,那么此時(shí)會(huì)通過(guò)閉包(函數(shù)科里化)的形式將modules, nodeOps和各種方法進(jìn)行鎖定,多次調(diào)用patch
也只會(huì)初始化一次,避免多次多用調(diào)用多次賦值。
通過(guò)條件判斷,在當(dāng)前例子中,會(huì)進(jìn)入createElm
函數(shù)。
9、createElm函數(shù)
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) } }
在當(dāng)前例子中,會(huì)執(zhí)行createChildren(vnode, children, insertedVnodeQueue)
進(jìn)行當(dāng)前節(jié)點(diǎn)子元素的遍歷,createChildren
中又遞歸的執(zhí)行了createElm
函數(shù),createElm
方法葉子節(jié)點(diǎn)頂端就是text文本,將執(zhí)行:
vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm);
將生成的文本節(jié)點(diǎn)賦值給vnode.elm,并將其插入到父節(jié)點(diǎn)中,一層一層遞歸,子節(jié)點(diǎn)中的DOM節(jié)點(diǎn)始終將向父級(jí)插入,遞歸結(jié)束時(shí),父節(jié)點(diǎn)將擁有一顆當(dāng)前數(shù)據(jù)下的完整DOM樹(shù)。 此時(shí)頁(yè)面中的視圖是:
{{message}}
Hello Vue!
還差最后一步,就是退出當(dāng)前的createElm
函數(shù)回到patch
函數(shù),執(zhí)行最后的移除操作。
10、移除
// destroy old node if (isDef(parentElm)) { removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) }
該方法將{{message}}從其父節(jié)點(diǎn)中移出。
小結(jié):從數(shù)據(jù)到模板渲染,主要流程是將模板轉(zhuǎn)換成render函數(shù)、將render函數(shù)轉(zhuǎn)換成虛擬DOM以及將虛擬DOM進(jìn)行視圖渲染。
以上就是vue2從數(shù)據(jù)到視圖渲染之模板渲染詳解的詳細(xì)內(nèi)容,更多關(guān)于vue2數(shù)據(jù)視圖模板渲染的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue項(xiàng)目中在父組件中直接調(diào)用子組件的方法
這篇文章主要給大家介紹了Vue項(xiàng)目中如何在父組件中直接調(diào)用子組件的方法,文章通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-11-11解決vue項(xiàng)目跳轉(zhuǎn)同樣的頁(yè)面不刷新的問(wèn)題思路詳解
做公司官網(wǎng)項(xiàng)目的時(shí)候遇到的場(chǎng)景,頂部導(dǎo)航欄分類商品跳轉(zhuǎn)到分類詳情,然后在分類詳情再次點(diǎn)擊頂部導(dǎo)航欄里另外的分類商品,跳到同樣的頁(yè)面數(shù)據(jù)不刷新,下面小編給大家分享解決方式,關(guān)于vue跳轉(zhuǎn)不刷新問(wèn)題感興趣的朋友一起看看吧2023-09-09vue使用async/await來(lái)實(shí)現(xiàn)同步和異步的案例分享
近期項(xiàng)目中大量使用async,從服務(wù)器獲取數(shù)據(jù),解決一些并發(fā)傳參問(wèn)題,代碼很簡(jiǎn)單,在此也看了一些博客,現(xiàn)在async/await已經(jīng)大范圍讓使用,所以本文給大家介紹一下vue使用async/await來(lái)實(shí)現(xiàn)同步和異步的案例,需要的朋友可以參考下2024-01-01Vue使用Proxy監(jiān)聽(tīng)所有接口狀態(tài)的方法實(shí)現(xiàn)
這篇文章主要介紹了Vue使用Proxy監(jiān)聽(tīng)所有接口狀態(tài)的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06vue2實(shí)現(xiàn)無(wú)感刷新token的方式詳解
在Web應(yīng)用中,用戶需要通過(guò)認(rèn)證和授權(quán)才能訪問(wèn)受保護(hù)的資源,為了實(shí)現(xiàn)認(rèn)證和授權(quán)功能,通常需要使用Token來(lái)標(biāo)識(shí)用戶身份并驗(yàn)證其權(quán)限,本文給大家介紹了vue2實(shí)現(xiàn)無(wú)感刷新token的方式,需要的朋友可以參考下2024-02-02基于vue.js仿淘寶收貨地址并設(shè)置默認(rèn)地址的案例分析
這篇文章主要介紹了基于vue.js仿淘寶收貨地址并設(shè)置默認(rèn)地址的案例分析,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08vue props傳值失敗 輸出undefined的解決方法
今天小編就為大家分享一篇vue props傳值失敗 輸出undefined的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09