摘要:組件初始化渲染本文以局部組件的注冊方式介紹組件的初始化渲染,如下源碼解析一模版渲染介紹過,初始化時根據函數生成函數,本文函數會調用,判斷是注冊過的組件,因此以組件的方式生成生成的函數會調用本例,在屬性中注冊過,因此以組件的
組件初始化渲染
本文以局部組件的注冊方式介紹組件的初始化渲染,demo如下
new Vue({ el: "#app", template: ``, components:{ "my-component": { template: "father component! children component!" } } })
1、Vue源碼解析(一)-模版渲染介紹過,vue初始化時根據template函數生成render函數,本文render函數會調用vm._c("my-component"),_createElement判斷"my-component是注冊過的組件,因此以組件的方式生成vnode
updateComponent = function () { vm._update(vm._render(), hydrating); }; //template生成的render函數vm._render會調用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屬性中注冊過,因此以組件的方式生成vnode if (isDef(Ctor = resolveAsset(context.$options, "components", tag))) { vnode = createComponent(Ctor, data, context, children, tag); } } //本例Ctor參數{template: "children component1!"} function createComponent (Ctor){ //Vue構造函數 var baseCtor = context.$options._base; if (isObject(Ctor)) { //生成VuComponent構造函數 //此處相當于Ctor = Vue.extend({template: "children component1!"}), Vue.extend后面有介紹; Ctor = baseCtor.extend(Ctor); } //將componentVNodeHooks上的方法掛載到vnode上,組件初次渲染會用到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){ //根據Vnode生成VueComponent實例 var child = vnode.componentInstance = createComponentInstanceForVnode(vnode); //將VueComponent實例掛載到dom節點上,本文是掛載到節點 child.$mount(hydrating ? vnode.elm : undefined, hydrating); } }
2、調用vm._update將vnode渲染為瀏覽器dom,主要方法是遍歷vnode的所有節點,根據節點類型調用相關的方法進行解析,本文主要介紹components的解析方法createComponent:根據vnode生成VueComponent(繼承Vue)對象,
調用Vue.prototype.$mount方法渲染dom
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { //組件vnode節點渲染方法 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } //Vue源碼解析(一)中介紹過普通vnode節點渲染步驟 //根據vnode節點生成瀏覽器Element對象 vnode.elm = nodeOps.createElement(tag, vnode); var children = vnode.children; //遞歸將vnode子節點生成Element對象 createChildren(vnode, children, insertedVnodeQueue); //將生成的vnode.elm插入到瀏覽器的父節點當中 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構造函數Ctor,相當于new VueComponent(options) return new vnode.ComponentOptions.Ctor(options) }
3 、new VueComponent和new Vue的過程類似,本文就不再做介紹
全局注冊組件上文提到過 Vue.extend方法(繼承Vue生成VueComponent構造函數)此處多帶帶介紹一下
Vue.extend = function (extendOptions) { var Super = this; var Sub = function VueComponent (options) { this._init(options); }; //經典的繼承寫法 Sub.prototype = Object.create(Super.prototype); Sub.prototype.constructor = Sub; Sub.options = mergeOptions( Super.options, extendOptions ); return Sub };
通過Vue.component也可以全局注冊組件,不需要每次new vue的時候多帶帶注冊,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初始化時會先調用一次initGlobalAPI(Vue),給Vue構造函數掛載上一些全局的api,其中又會調用到
initAssetRegisters(Vue),其中定義了Vue.component方法,具體看下其實現
var ASSET_TYPES = [ "component", "directive", "filter" ]; //循環注冊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 }; } //全局的組件、指令和過濾器都掛載在Vue.options上 this.options[type + "s"][id] = definition; return definition } }; }); Vue.prototype._init = function (options) { vue初始化時將options參數和Vue.options組裝為vm.$options vm.$options = mergeOptions( //Vue.options resolveConstructorOptions(vm.constructor), options || {}, vm ); }
本例組裝后的vm.$option.components值如下,proto中前3個屬性是內置全局組件
在 Vue 中,父子組件的關系可以總結為 prop 向下傳遞,事件向上傳遞。父組件通過 prop 給子組件下發數據,子組件通過事件給父組件發送消息.先看看prop是怎么工作的。demo如下:
new Vue({ el: "#app", template: ``, components:{ "my-component":{ props: ["message"], template: "{{ message }}" } } })father component!
1、template生成的render函數包含:_c("my-component",{attrs:{"message":"hello!"}})]
2、render => vnode => VueComponent,上文提到的VueComponent的構造函數調用了Vue.prototype._init,并且入參option.propsData:{message: "hello!"}
3、雙向綁定中介紹過Vue初始化時會對data中的所有屬性調用defineReactive方法,對data屬性進行監聽;
VueComponent對propsData也是類似的處理方法,initProps后propsData中的屬性和data一樣也是響應式的,propsData變化,相應的view也會發生改變
function initProps (vm, propsOptions) { for (var key in propsOptions){ //defineReactive參照Vue源碼解析(二) defineReactive(props, key, value); //將propsData代理到vm上,通過vm[key]訪問propsData[key] proxy(vm, "_props", key); } }
4、propsData是響應式的了,但更常用的是動態props,按官網說法:“我們可以用v-bind來動態地將prop綁定到父組件的數據。每當父組件的數據變化時,該變化也會傳導給子組件”,那么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、雙向綁定中介紹過vm.parentMsg變化,會觸發dep.notify(),通知watcher調用updateComponent;
又回到了updateComponent,之后的dom更新過程可以參考上文的組件渲染邏輯,只是propsData值已經是最新的vm.parentMsg的值了
//又見到了。。所有的dom初始化或更新都會用到 updateComponent = function () { vm._update(vm._render(), hydrating); }; Vue.prototype._update = function (vnode, hydrating) { var prevVnode = vm._vnode; vm._vnode = vnode; //Vue源碼解析(一)介紹過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源碼解析(一)介紹過vm.__patch__中dom初始化渲染的邏輯,本文再簡單介紹下vm.__patch關于component更新的邏輯:
function patchVnode (oldVnode, vnode){ //上文介紹過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, //此時的propsData已經是最新的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]設置為新的propsData[key]值,從而觸發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結束會觸發callHook(vm, "mounted"),調用this.$emit("rf","hello"),從而調用父組件的receiveFn方法
Vue.prototype.$emit = function (event) { //本例cbs=vm._events["rf"] = receiveFn,vm._events涉及v-on指令解析,以后有機會詳細介紹下 var cbs = vm._events[event]; //截取第一位之后的參數 var args = toArray(arguments, 1); //執行cbs cbs.apply(vm, args); }event bus
prop和emit是父子組件通信的方式,非父子組件可以通過event bus(事件總線)實現
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方法上文已經介紹過,主要看下on方法,其實就是將fn注冊到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 };
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89782.html
摘要:是響應式的,當瀏覽器路由改變時,的值也會相應的改變的作用是清楚了,但頁面內容的變化是怎么實現的呢下面再介紹下的作用。 先上一段簡單的demo,本文根據此demo進行解析 Vue.use(VueRouter) const router = new VueRouter({ routes: [ { path: /home, component: {template: ...
摘要:直接寫了組件機制。今天看了下的關于事件的機制。源碼都是基于最新的。綁定了事件回調函數的。初始化的時候,將中的方法代理到的同時修飾了事件的回調函數。對于事件有兩個底層的處理邏輯。 上一章沒什么經驗。直接寫了組件機制。感覺涉及到的東西非常的多,不是很方便講。今天看了下vue的關于事件的機制。有一些些體會。寫出來。大家一起糾正,分享。源碼都是基于最新的Vue.js v2.3.0。下面我們來看...
摘要:定義一個組件如下打印如下再回過頭看,可以發現他做的工作就是擴展一個構造函數,并將這個構造函數添加到現在我們已經可以回答最開始的問題的組件是什么的組件其實就是擴展的構造函數,并且在適當的時候實例化為實例。 vue@2.0源碼學習---組件究竟是什么 本篇文章從最簡單的情況入手,不考慮prop和組件間通信。 Vue.component vue文檔告訴我們可以使用Vue.component(...
摘要:提供了兩種向組件傳遞參數的方式。子路由項路徑不要使用開頭,以開頭的嵌套路徑會被當作根路徑。路由實例的方法這里學習兩個路由實例的方法和。實際上,是通過不同的將這些資源加載后打包,然后輸出打包后文件。 一、vue-router 1、簡介 我們經常使用vue開發單頁面應用程序(SPA)。在開發SPA過程中,路由是必不可少的部分,vue的官方推薦是vue-router。單頁面應用程序看起來好像...
摘要:提供了兩種向組件傳遞參數的方式。子路由項路徑不要使用開頭,以開頭的嵌套路徑會被當作根路徑。路由實例的方法這里學習兩個路由實例的方法和。實際上,是通過不同的將這些資源加載后打包,然后輸出打包后文件。 一、vue-router 1、簡介 我們經常使用vue開發單頁面應用程序(SPA)。在開發SPA過程中,路由是必不可少的部分,vue的官方推薦是vue-router。單頁面應用程序看起來好像...
閱讀 2533·2021-10-09 09:44
閱讀 640·2019-08-30 15:44
閱讀 2994·2019-08-29 18:46
閱讀 1133·2019-08-29 18:38
閱讀 557·2019-08-26 10:44
閱讀 2431·2019-08-23 16:07
閱讀 1089·2019-08-23 15:38
閱讀 4085·2019-08-23 14:02