Vue中v-bind原理深入探究
前面我們分析了v-model的原理,接下來我們看看v-bind的實現(xiàn)又是怎樣的呢?
前置內(nèi)容
<template> <div> <test :propTest="a"></test> <div @click="changeA">點我</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("點我")]), ], 1 ) }
可以看出v-on:propTest='a’會被解析成attrs: { propTest: _vm.a },看過前幾篇文章的都知道會觸發(fā)a變量的get方法收集依賴。目前主要是看當(dāng)前組件是怎么把attrs屬性傳遞給子組件的:
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ù)。這個在后面實例化test的vm函數(shù)時有用
此時所有vnode基本創(chuàng)建完畢。此時執(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; } ... }
由于第一個node是test是一個組件,所有會執(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實例:
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); }
實例化過程中使用了vnode過程中創(chuàng)建的vm函數(shù),在實例化的過程中會執(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方法校驗數(shù)據(jù)和我們在test組件中定義的props類型是否相同。然后執(zhí)行defineReactive方法將該prop設(shè)置在vm._props中并設(shè)置get和set。
此時我們得出一個結(jié)論,test組件會先根據(jù)props校驗propsData的類型并獲取值,test組件定義的props會被設(shè)置響應(yīng)式。至此App.vue中的v-on中給的值已經(jīng)被傳到test組件并設(shè)置了初始值。
不難看出,當(dāng)App.vue中的數(shù)據(jù)發(fā)生變化時會重新執(zhí)行變量中的watcher的update方法重新將值傳入test組件。由于該組件已經(jīng)創(chuàng)建了會存在prevVnode值,所以不會再次創(chuàng)建只會執(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); } ... };
至此整個過程結(jié)束。
總結(jié)
- 會將v-on:propTest="a"解析成attrs: { propTest: _vm.a },并在渲染組件test的時候把該值傳過去。
- 在實例化組件的時候會根據(jù)props里面創(chuàng)建的值和傳進來的值做類型校驗,然后并設(shè)置響應(yīng)式同時設(shè)置初始值。
- 父組件值變化的時候會觸發(fā)該變量的set方法父組件執(zhí)行updateChildren方法對比新舊子組件并更新值。
到此這篇關(guān)于Vue中v-bind原理深入探究的文章就介紹到這了,更多相關(guān)Vue v-bind內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue+tsc+noEmit導(dǎo)致打包報TS類型錯誤問題及解決方法
當(dāng)我們新建vue3項目,package.json文件會自動給我添加一些配置選項,這寫選項基本沒有問題,但是在實際操作過程中,當(dāng)項目越來越復(fù)雜就會出現(xiàn)問題,本文給大家分享vue+tsc+noEmit導(dǎo)致打包報TS類型錯誤問題及解決方法,感興趣的朋友一起看看吧2023-10-10Vue Router動態(tài)路由使用方法總結(jié)
這篇文章主要介紹了Vue Router動態(tài)路由使用方法總結(jié),需要的朋友可以參考下2023-10-10vue3之Suspense加載異步數(shù)據(jù)的使用
本文主要介紹了vue3之Suspense加載異步數(shù)據(jù)的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02vue?cli+axios踩坑記錄+攔截器使用方式,代理跨域proxy
這篇文章主要介紹了vue?cli+axios踩坑記錄+攔截器使用方式,代理跨域proxy,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04