摘要:組件初始化渲染本文以局部組件的注冊(cè)方式介紹組件的初始化渲染,如下源碼解析一模版渲染介紹過(guò),初始化時(shí)根據(jù)函數(shù)生成函數(shù),本文函數(shù)會(huì)調(diào)用,判斷是注冊(cè)過(guò)的組件,因此以組件的方式生成生成的函數(shù)會(huì)調(diào)用本例,在屬性中注冊(cè)過(guò),因此以組件的
組件初始化渲染
本文以局部組件的注冊(cè)方式介紹組件的初始化渲染,demo如下
new Vue({ el: "#app", template: ``, components:{ "my-component": { template: "father component! children component!" } } })
1、Vue源碼解析(一)-模版渲染介紹過(guò),vue初始化時(shí)根據(jù)template函數(shù)生成render函數(shù),本文render函數(shù)會(huì)調(diào)用vm._c("my-component"),_createElement判斷"my-component是注冊(cè)過(guò)的組件,因此以組件的方式生成vnode
updateComponent = function () { vm._update(vm._render(), hydrating); }; //template生成的render函數(shù)vm._render會(huì)調(diào)用vm._c("my-component") vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }; function _createElement(){ //本例tag=‘my-component’,‘my-component’在components屬性中注冊(cè)過(guò),因此以組件的方式生成vnode if (isDef(Ctor = resolveAsset(context.$options, "components", tag))) { vnode = createComponent(Ctor, data, context, children, tag); } } //本例Ctor參數(shù){template: "children component1!"} function createComponent (Ctor){ //Vue構(gòu)造函數(shù) var baseCtor = context.$options._base; if (isObject(Ctor)) { //生成VuComponent構(gòu)造函數(shù) //此處相當(dāng)于Ctor = Vue.extend({template: "children component1!"}), Vue.extend后面有介紹; Ctor = baseCtor.extend(Ctor); } //將componentVNodeHooks上的方法掛載到vnode上,組件初次渲染會(huì)用到componentVNodeHooks.init var data = {} mergeHooks(data); var vnode = new VNode( ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : "")), data, undefined, undefined, undefined, context, { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children } ); } //component初始化和更新的方法,此處先介紹init var componentVNodeHooks = { init(vnode){ //根據(jù)Vnode生成VueComponent實(shí)例 var child = vnode.componentInstance = createComponentInstanceForVnode(vnode); //將VueComponent實(shí)例掛載到dom節(jié)點(diǎn)上,本文是掛載到節(jié)點(diǎn) child.$mount(hydrating ? vnode.elm : undefined, hydrating); } }
2、調(diào)用vm._update將vnode渲染為瀏覽器dom,主要方法是遍歷vnode的所有節(jié)點(diǎn),根據(jù)節(jié)點(diǎn)類型調(diào)用相關(guān)的方法進(jìn)行解析,本文主要介紹components的解析方法createComponent:根據(jù)vnode生成VueComponent(繼承Vue)對(duì)象,
調(diào)用Vue.prototype.$mount方法渲染dom
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { //組件vnode節(jié)點(diǎn)渲染方法 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } //Vue源碼解析(一)中介紹過(guò)普通vnode節(jié)點(diǎn)渲染步驟 //根據(jù)vnode節(jié)點(diǎn)生成瀏覽器Element對(duì)象 vnode.elm = nodeOps.createElement(tag, vnode); var children = vnode.children; //遞歸將vnode子節(jié)點(diǎn)生成Element對(duì)象 createChildren(vnode, children, insertedVnodeQueue); //將生成的vnode.elm插入到瀏覽器的父節(jié)點(diǎn)當(dāng)中 insert(parentElm, vnode.elm, refElm); } function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { var i = vnode.data; if (isDef(i = i.hook) && isDef(i = i.init)) { //i就是上面的componentVNodeHooks.init方法 i(vnode, false /* hydrating */, parentElm, refElm); } if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue); if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm); } return true } } function createComponentInstanceForVnode (){ var options = { _isComponent: true, parent: parent, propsData: vnodeComponentOptions.propsData, _componentTag: vnodeComponentOptions.tag, _parentVnode: vnode, _parentListeners: vnodeComponentOptions.listeners, _renderChildren: vnodeComponentOptions.children, _parentElm: parentElm || null, _refElm: refElm || null }; //上面提到的VueComponent構(gòu)造函數(shù)Ctor,相當(dāng)于new VueComponent(options) return new vnode.ComponentOptions.Ctor(options) }
3 、new VueComponent和new Vue的過(guò)程類似,本文就不再做介紹
全局注冊(cè)組件上文提到過(guò) Vue.extend方法(繼承Vue生成VueComponent構(gòu)造函數(shù))此處多帶帶介紹一下
Vue.extend = function (extendOptions) { var Super = this; var Sub = function VueComponent (options) { this._init(options); }; //經(jīng)典的繼承寫法 Sub.prototype = Object.create(Super.prototype); Sub.prototype.constructor = Sub; Sub.options = mergeOptions( Super.options, extendOptions ); return Sub };
通過(guò)Vue.component也可以全局注冊(cè)組件,不需要每次new vue的時(shí)候多帶帶注冊(cè),demo如下:
var globalComponent = Vue.extend({ name: "global-component", template: "global component!" }); Vue.component("global-component", globalComponent); new Vue({ el: "#app", template: ``, components:{ "my-component": { template: " children component!" } } })
vue.js初始化時(shí)會(huì)先調(diào)用一次initGlobalAPI(Vue),給Vue構(gòu)造函數(shù)掛載上一些全局的api,其中又會(huì)調(diào)用到
initAssetRegisters(Vue),其中定義了Vue.component方法,具體看下其實(shí)現(xiàn)
var ASSET_TYPES = [ "component", "directive", "filter" ]; //循環(huán)注冊(cè)ASSET_TYPES中的全局方法 ASSET_TYPES.forEach(function (type) { Vue[type] = function ( id, definition ) { if (!definition) { return this.options[type + "s"][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && type === "component") { validateComponentName(id); } if (type === "component" && isPlainObject(definition)) { definition.name = definition.name || id; definition = this.options._base.extend(definition); } if (type === "directive" && typeof definition === "function") { definition = { bind: definition, update: definition }; } //全局的組件、指令和過(guò)濾器都掛載在Vue.options上 this.options[type + "s"][id] = definition; return definition } }; }); Vue.prototype._init = function (options) { vue初始化時(shí)將options參數(shù)和Vue.options組裝為vm.$options vm.$options = mergeOptions( //Vue.options resolveConstructorOptions(vm.constructor), options || {}, vm ); }
本例組裝后的vm.$option.components值如下,proto中前3個(gè)屬性是內(nèi)置全局組件
在 Vue 中,父子組件的關(guān)系可以總結(jié)為 prop 向下傳遞,事件向上傳遞。父組件通過(guò) prop 給子組件下發(fā)數(shù)據(jù),子組件通過(guò)事件給父組件發(fā)送消息.先看看prop是怎么工作的。demo如下:
new Vue({ el: "#app", template: ``, components:{ "my-component":{ props: ["message"], template: "{{ message }}" } } })father component!
1、template生成的render函數(shù)包含:_c("my-component",{attrs:{"message":"hello!"}})]
2、render => vnode => VueComponent,上文提到的VueComponent的構(gòu)造函數(shù)調(diào)用了Vue.prototype._init,并且入?yún)ption.propsData:{message: "hello!"}
3、雙向綁定中介紹過(guò)Vue初始化時(shí)會(huì)對(duì)data中的所有屬性調(diào)用defineReactive方法,對(duì)data屬性進(jìn)行監(jiān)聽;
VueComponent對(duì)propsData也是類似的處理方法,initProps后propsData中的屬性和data一樣也是響應(yīng)式的,propsData變化,相應(yīng)的view也會(huì)發(fā)生改變
function initProps (vm, propsOptions) { for (var key in propsOptions){ //defineReactive參照Vue源碼解析(二) defineReactive(props, key, value); //將propsData代理到vm上,通過(guò)vm[key]訪問(wèn)propsData[key] proxy(vm, "_props", key); } }
4、propsData是響應(yīng)式的了,但更常用的是動(dòng)態(tài)props,按官網(wǎng)說(shuō)法:“我們可以用v-bind來(lái)動(dòng)態(tài)地將prop綁定到父組件的數(shù)據(jù)。每當(dāng)父組件的數(shù)據(jù)變化時(shí),該變化也會(huì)傳導(dǎo)給子組件”,那么vue是如何將data的變化傳到給自組件的呢,先看demo
var vm = new Vue({ el: "#app", template: ``, data(){ return{ parentMsg:"hello" } }, components:{ "my-component":{ props: ["message"], template: "{{ message }}" } } }) vm.parentMsg = "hello world"
5、雙向綁定中介紹過(guò)vm.parentMsg變化,會(huì)觸發(fā)dep.notify(),通知watcher調(diào)用updateComponent;
又回到了updateComponent,之后的dom更新過(guò)程可以參考上文的組件渲染邏輯,只是propsData值已經(jīng)是最新的vm.parentMsg的值了
//又見(jiàn)到了。。所有的dom初始化或更新都會(huì)用到 updateComponent = function () { vm._update(vm._render(), hydrating); }; Vue.prototype._update = function (vnode, hydrating) { var prevVnode = vm._vnode; vm._vnode = vnode; //Vue源碼解析(一)介紹過(guò)dom初始化渲染的源碼 if (!prevVnode) { // initial render vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false /* removeOnly */, vm.$options._parentElm, vm.$options._refElm ); } else { // 本文介紹dom更新的方法 vm.$el = vm.__patch__(prevVnode, vnode); } }
Vue源碼解析(一)介紹過(guò)vm.__patch__中dom初始化渲染的邏輯,本文再簡(jiǎn)單介紹下vm.__patch關(guān)于component更新的邏輯:
function patchVnode (oldVnode, vnode){ //上文介紹過(guò)componentVNodeHooks.init,此處i=componentVNodeHooks.prepatch var data = vnode.data; if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode); } } var componentVNodeHooks = { init(){}, prepatch: function prepatch (oldVnode, vnode) { var options = vnode.componentOptions; var child = vnode.componentInstance = oldVnode.componentInstance; //更新組件 updateChildComponent( child, //此時(shí)的propsData已經(jīng)是最新的vm.parentMsg options.propsData, // updated props options.listeners, // updated listeners vnode, // new parent vnode options.children // new children ); } } function updateChildComponent (vm, propsData){ //將vm._props[key]設(shè)置為新的propsData[key]值,從而觸發(fā)view層的更新 var props = vm._props; props[key] = validateProp(key, vm.$options.props, propsData, vm); }emit
子組件向父組件通信需要用到emit,先給出demo
var vm = new Vue({ el: "#app", template: ``, methods:{ receiveFn(msg){ console.log(msg) } }, components:{ "my-component":{ template: " child", mounted(){ this.$emit("rf","hello") } } } })
本例中子組件mount結(jié)束會(huì)觸發(fā)callHook(vm, "mounted"),調(diào)用this.$emit("rf","hello"),從而調(diào)用父組件的receiveFn方法
Vue.prototype.$emit = function (event) { //本例cbs=vm._events["rf"] = receiveFn,vm._events涉及v-on指令解析,以后有機(jī)會(huì)詳細(xì)介紹下 var cbs = vm._events[event]; //截取第一位之后的參數(shù) var args = toArray(arguments, 1); //執(zhí)行cbs cbs.apply(vm, args); }event bus
prop和emit是父子組件通信的方式,非父子組件可以通過(guò)event bus(事件總線)實(shí)現(xiàn)
var bus = new Vue(); var vm = new Vue({ el: "#app", template: ``, components:{ "my-component-1":{ template: " child1", mounted(){ bus.$on("event",(msg)=>{ console.log(msg) }) } }, "my-component-2":{ template: "child2", mounted(){ bus.$emit("event","asd") } } } })
emit方法上文已經(jīng)介紹過(guò),主要看下on方法,其實(shí)就是將fn注冊(cè)到vm._events上
Vue.prototype.$on = function (event, fn) { var vm = this; if (Array.isArray(event)) { for (var i = 0, l = event.length; i < l; i++) { this.$on(event[i], fn); } } else { (vm._events[event] || (vm._events[event] = [])).push(fn); } return vm };
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/89782.html
摘要:是響應(yīng)式的,當(dāng)瀏覽器路由改變時(shí),的值也會(huì)相應(yīng)的改變的作用是清楚了,但頁(yè)面內(nèi)容的變化是怎么實(shí)現(xiàn)的呢下面再介紹下的作用。 先上一段簡(jiǎn)單的demo,本文根據(jù)此demo進(jìn)行解析 Vue.use(VueRouter) const router = new VueRouter({ routes: [ { path: /home, component: {template: ...
摘要:直接寫了組件機(jī)制。今天看了下的關(guān)于事件的機(jī)制。源碼都是基于最新的。綁定了事件回調(diào)函數(shù)的。初始化的時(shí)候,將中的方法代理到的同時(shí)修飾了事件的回調(diào)函數(shù)。對(duì)于事件有兩個(gè)底層的處理邏輯。 上一章沒(méi)什么經(jīng)驗(yàn)。直接寫了組件機(jī)制。感覺(jué)涉及到的東西非常的多,不是很方便講。今天看了下vue的關(guān)于事件的機(jī)制。有一些些體會(huì)。寫出來(lái)。大家一起糾正,分享。源碼都是基于最新的Vue.js v2.3.0。下面我們來(lái)看...
摘要:定義一個(gè)組件如下打印如下再回過(guò)頭看,可以發(fā)現(xiàn)他做的工作就是擴(kuò)展一個(gè)構(gòu)造函數(shù),并將這個(gè)構(gòu)造函數(shù)添加到現(xiàn)在我們已經(jīng)可以回答最開始的問(wèn)題的組件是什么的組件其實(shí)就是擴(kuò)展的構(gòu)造函數(shù),并且在適當(dāng)?shù)臅r(shí)候?qū)嵗癁閷?shí)例。 vue@2.0源碼學(xué)習(xí)---組件究竟是什么 本篇文章從最簡(jiǎn)單的情況入手,不考慮prop和組件間通信。 Vue.component vue文檔告訴我們可以使用Vue.component(...
摘要:提供了兩種向組件傳遞參數(shù)的方式。子路由項(xiàng)路徑不要使用開頭,以開頭的嵌套路徑會(huì)被當(dāng)作根路徑。路由實(shí)例的方法這里學(xué)習(xí)兩個(gè)路由實(shí)例的方法和。實(shí)際上,是通過(guò)不同的將這些資源加載后打包,然后輸出打包后文件。 一、vue-router 1、簡(jiǎn)介 我們經(jīng)常使用vue開發(fā)單頁(yè)面應(yīng)用程序(SPA)。在開發(fā)SPA過(guò)程中,路由是必不可少的部分,vue的官方推薦是vue-router。單頁(yè)面應(yīng)用程序看起來(lái)好像...
摘要:提供了兩種向組件傳遞參數(shù)的方式。子路由項(xiàng)路徑不要使用開頭,以開頭的嵌套路徑會(huì)被當(dāng)作根路徑。路由實(shí)例的方法這里學(xué)習(xí)兩個(gè)路由實(shí)例的方法和。實(shí)際上,是通過(guò)不同的將這些資源加載后打包,然后輸出打包后文件。 一、vue-router 1、簡(jiǎn)介 我們經(jīng)常使用vue開發(fā)單頁(yè)面應(yīng)用程序(SPA)。在開發(fā)SPA過(guò)程中,路由是必不可少的部分,vue的官方推薦是vue-router。單頁(yè)面應(yīng)用程序看起來(lái)好像...
閱讀 2544·2021-10-09 09:44
閱讀 644·2019-08-30 15:44
閱讀 3005·2019-08-29 18:46
閱讀 1142·2019-08-29 18:38
閱讀 565·2019-08-26 10:44
閱讀 2437·2019-08-23 16:07
閱讀 1100·2019-08-23 15:38
閱讀 4114·2019-08-23 14:02