Vue中v-bind原理深入探究
前面我們分析了v-model的原理,接下來我們看看v-bind的實(shí)現(xiàn)又是怎樣的呢?
前置內(nèi)容
<template> <div> <test :propTest="a"></test> <div @click="changeA">點(diǎn)我</div> </div> </template> <script> import test from './test.vue' export default { name: "TestWebpackTest", components:{ test }, mounted() { console.log(this); }, methods:{ changeA(){ this.a = Math.random() } }, data() { return { a:111, }; } }; </script> ... <template> <div> {{propTest}} </div> </template> <script> export default { name: 'test', mounted() { console.log(this); }, props:{ propTest:Number } } </script> <style> </style>
解析模板
// App.vue var render = function render() { var _vm = this, _c = _vm._self._c return _c( "div", [ _c("test", { attrs: { propTest: _vm.a } }), _vm._v(" "), _c("div", { on: { click: _vm.changeA } }, [_vm._v("點(diǎn)我")]), ], 1 ) }
可以看出v-on:propTest='a’會(huì)被解析成attrs: { propTest: _vm.a },看過前幾篇文章的都知道會(huì)觸發(fā)a變量的get方法收集依賴。目前主要是看當(dāng)前組件是怎么把a(bǔ)ttrs屬性傳遞給子組件的:
function createElement$1(context, tag, data, children, normalizationType, alwaysNormalize) { if (isArray(data) || isPrimitive(data)) { normalizationType = children; children = data; data = undefined; } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE; } return _createElement(context, tag, data, children, normalizationType); }
_c(“test”, { attrs: { propTest: _vm.a } })方法主要執(zhí)行createElement$1方法:
function _createElement(context, tag, data, children, normalizationType) { ... else if ((!data || !data.pre) && isDef((Ctor = resolveAsset(context.$options, 'components', tag)))) { // component vnode = createComponent(Ctor, data, context, children, tag); } ... if (isArray(vnode)) { return vnode; } else if (isDef(vnode)) { if (isDef(ns)) applyNS(vnode, ns); if (isDef(data)) registerDeepBindings(data); return vnode; } else { return createEmptyVNode(); } }
主要執(zhí)行createComponent方法,傳入?yún)?shù)如圖所示:
接下來執(zhí)行createComponent函數(shù),并創(chuàng)建test的vm函數(shù)然后創(chuàng)建test的vnode:
function createComponent(Ctor, data, context, children, tag) { ... var baseCtor = context.$options._base; // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); } ... data = data || {}; // resolve constructor options in case global mixins are applied after // component constructor creation resolveConstructorOptions(Ctor); // transform component v-model data into props & events if (isDef(data.model)) { // @ts-expect-error transformModel(Ctor.options, data); } // extract props // @ts-expect-error var propsData = extractPropsFromVNodeData(data, Ctor, tag); // functional component // @ts-expect-error if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children); } // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners var listeners = data.on; // replace with listeners with .native modifier // so it gets processed during parent component patch. data.on = data.nativeOn; // @ts-expect-error if (isTrue(Ctor.options.abstract)) { // abstract components do not keep anything // other than props & listeners & slot // work around flow var slot = data.slot; data = {}; if (slot) { data.slot = slot; } } // install component management hooks onto the placeholder node installComponentHooks(data); // return a placeholder vnode // @ts-expect-error var name = getComponentName(Ctor.options) || tag; var vnode = new VNode( // @ts-expect-error "vue-component-".concat(Ctor.cid).concat(name ? "-".concat(name) : ''), data, undefined, undefined, undefined, context, // @ts-expect-error { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory); return vnode; }
創(chuàng)建的vnode如下圖所示,其中componetnOptions是創(chuàng)建vm函數(shù)時(shí)的參數(shù)。這個(gè)在后面實(shí)例化test的vm函數(shù)時(shí)有用
此時(shí)所有vnode基本創(chuàng)建完畢。此時(shí)執(zhí)行vm._update(vm._render(), hydrating)方法,該方法主要執(zhí)行vm.$el = vm._patch_(prevVnode, vnode)方法,該方法執(zhí)行createChildren去遍歷vnode執(zhí)行createElm方法:
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) { ... if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return; } ... }
由于第一個(gè)node是test是一個(gè)組件,所有會(huì)執(zhí)行createComponent方法:
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) { var i = vnode.data; if (isDef(i)) { var isReactivated = isDef(vnode.componentInstance) && i.keepAlive; if (isDef((i = i.hook)) && isDef((i = i.init))) { i(vnode, false /* hydrating */); } ... } } ... init: function (vnode, hydrating) { ... else { var child = (vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)); child.$mount(hydrating ? vnode.elm : undefined, hydrating); } }
該方法執(zhí)行componentVNodeHooks的init方法的createComponentInstanceForVnode去創(chuàng)建test組件的vm實(shí)例:
function createComponentInstanceForVnode(parent) { var options = { _isComponent: true, _parentVnode: vnode, parent: parent }; // check inline-template render functions var inlineTemplate = vnode.data.inlineTemplate; if (isDef(inlineTemplate)) { options.render = inlineTemplate.render; options.staticRenderFns = inlineTemplate.staticRenderFns; } return new vnode.componentOptions.Ctor(options); }
實(shí)例化過程中使用了vnode過程中創(chuàng)建的vm函數(shù),在實(shí)例化的過程中會(huì)執(zhí)行initInternalComponent函數(shù),該函數(shù)從父vnode的componentOptions中獲取prop數(shù)據(jù):
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); }
到這里為止從App.vue中的attrs屬性就已經(jīng)傳到test組件上了。initInternalComponent方法執(zhí)行完畢繼續(xù)執(zhí)行initState方法:
function initState(vm) { var opts = vm.$options; if (opts.props) initProps$1(vm, opts.props); // Composition API initSetup(vm); if (opts.methods) initMethods(vm, opts.methods); if (opts.data) { initData(vm); } else { var ob = observe((vm._data = {})); ob && ob.vmCount++; } if (opts.computed) initComputed$1(vm, opts.computed); if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
首先執(zhí)行initProps$1方法:
function initProps$1(vm, propsOptions) { var propsData = vm.$options.propsData || {}; var props = (vm._props = shallowReactive({})); // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. var keys = (vm.$options._propKeys = []); var isRoot = !vm.$parent; // root instance props should be converted if (!isRoot) { toggleObserving(false); } var _loop_1 = function (key) { keys.push(key); var value = validateProp(key, propsOptions, propsData, vm); /* istanbul ignore else */ { var hyphenatedKey = hyphenate(key); if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn$2("\"".concat(hyphenatedKey, "\" is a reserved attribute and cannot be used as component prop."), vm); } defineReactive(props, key, value, function () { if (!isRoot && !isUpdatingChildComponent) { warn$2("Avoid mutating a prop directly since the value will be " + "overwritten whenever the parent component re-renders. " + "Instead, use a data or computed property based on the prop's " + "value. Prop being mutated: \"".concat(key, "\""), vm); } }); } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { proxy(vm, "_props", key); } }; for (var key in propsOptions) { _loop_1(key); } toggleObserving(true); }
獲取到propsOptions并循環(huán)執(zhí)行_loop_1(key)方法,該方法首先執(zhí)行validateProp方法校驗(yàn)數(shù)據(jù)和我們在test組件中定義的props類型是否相同。然后執(zhí)行defineReactive方法將該prop設(shè)置在vm._props中并設(shè)置get和set。
此時(shí)我們得出一個(gè)結(jié)論,test組件會(huì)先根據(jù)props校驗(yàn)propsData的類型并獲取值,test組件定義的props會(huì)被設(shè)置響應(yīng)式。至此App.vue中的v-on中給的值已經(jīng)被傳到test組件并設(shè)置了初始值。
不難看出,當(dāng)App.vue中的數(shù)據(jù)發(fā)生變化時(shí)會(huì)重新執(zhí)行變量中的watcher的update方法重新將值傳入test組件。由于該組件已經(jīng)創(chuàng)建了會(huì)存在prevVnode值,所以不會(huì)再次創(chuàng)建只會(huì)執(zhí)行vm._patch_(prevVnode, vnode)去更新組件的值:
Vue.prototype._update = function (vnode, hydrating) { var vm = this; var prevEl = vm.$el; var prevVnode = vm._vnode; var 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); } ... };
至此整個(gè)過程結(jié)束。
總結(jié)
- 會(huì)將v-on:propTest="a"解析成attrs: { propTest: _vm.a },并在渲染組件test的時(shí)候把該值傳過去。
- 在實(shí)例化組件的時(shí)候會(huì)根據(jù)props里面創(chuàng)建的值和傳進(jìn)來的值做類型校驗(yàn),然后并設(shè)置響應(yīng)式同時(shí)設(shè)置初始值。
- 父組件值變化的時(shí)候會(huì)觸發(fā)該變量的set方法父組件執(zhí)行updateChildren方法對(duì)比新舊子組件并更新值。
到此這篇關(guān)于Vue中v-bind原理深入探究的文章就介紹到這了,更多相關(guān)Vue v-bind內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
這15個(gè)Vue指令,讓你的項(xiàng)目開發(fā)爽到爆
這篇文章主要介紹了這15個(gè)Vue指令,讓你的項(xiàng)目開發(fā)爽到爆,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-10-10vue+tsc+noEmit導(dǎo)致打包報(bào)TS類型錯(cuò)誤問題及解決方法
當(dāng)我們新建vue3項(xiàng)目,package.json文件會(huì)自動(dòng)給我添加一些配置選項(xiàng),這寫選項(xiàng)基本沒有問題,但是在實(shí)際操作過程中,當(dāng)項(xiàng)目越來越復(fù)雜就會(huì)出現(xiàn)問題,本文給大家分享vue+tsc+noEmit導(dǎo)致打包報(bào)TS類型錯(cuò)誤問題及解決方法,感興趣的朋友一起看看吧2023-10-10Vue Router動(dòng)態(tài)路由使用方法總結(jié)
這篇文章主要介紹了Vue Router動(dòng)態(tài)路由使用方法總結(jié),需要的朋友可以參考下2023-10-10vue3之Suspense加載異步數(shù)據(jù)的使用
本文主要介紹了vue3之Suspense加載異步數(shù)據(jù)的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02基于mpvue搭建微信小程序項(xiàng)目框架的教程詳解
mpvue從底層支持 Vue.js 語法和構(gòu)建工具體系,同時(shí)再結(jié)合相關(guān)UI組件庫,便可以高效的實(shí)現(xiàn)小程序開發(fā)。這篇文章主要介紹了基于mpvue搭建小程序項(xiàng)目框架 ,需要的朋友可以參考下2019-04-04vue?cli+axios踩坑記錄+攔截器使用方式,代理跨域proxy
這篇文章主要介紹了vue?cli+axios踩坑記錄+攔截器使用方式,代理跨域proxy,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04