Vue中v-bind原理深入探究
作者:Young soul2
前面我们分析了v-model的原理,接下来我们看看v-bind的实现又是怎样的呢?
前置内容
<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 },看过前几篇文章的都知道会触发a变量的get方法收集依赖。目前主要是看当前组件是怎么把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 } })方法主要执行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(); } }
主要执行createComponent方法,传入参数如图所示:
接下来执行createComponent函数,并创建test的vm函数然后创建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; }
创建的vnode如下图所示,其中componetnOptions是创建vm函数时的参数。这个在后面实例化test的vm函数时有用
此时所有vnode基本创建完毕。此时执行vm._update(vm._render(), hydrating)方法,该方法主要执行vm.$el = vm._patch_(prevVnode, vnode)方法,该方法执行createChildren去遍历vnode执行createElm方法:
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) { ... if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return; } ... }
由于第一个node是test是一个组件,所有会执行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); } }
该方法执行componentVNodeHooks的init方法的createComponentInstanceForVnode去创建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过程中创建的vm函数,在实例化的过程中会执行initInternalComponent函数,该函数从父vnode的componentOptions中获取prop数据:
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属性就已经传到test组件上了。initInternalComponent方法执行完毕继续执行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); } }
首先执行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并循环执行_loop_1(key)方法,该方法首先执行validateProp方法校验数据和我们在test组件中定义的props类型是否相同。然后执行defineReactive方法将该prop设置在vm._props中并设置get和set。
此时我们得出一个结论,test组件会先根据props校验propsData的类型并获取值,test组件定义的props会被设置响应式。至此App.vue中的v-on中给的值已经被传到test组件并设置了初始值。
不难看出,当App.vue中的数据发生变化时会重新执行变量中的watcher的update方法重新将值传入test组件。由于该组件已经创建了会存在prevVnode值,所以不会再次创建只会执行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); } ... };
至此整个过程结束。
总结
- 会将v-on:propTest="a"解析成attrs: { propTest: _vm.a },并在渲染组件test的时候把该值传过去。
- 在实例化组件的时候会根据props里面创建的值和传进来的值做类型校验,然后并设置响应式同时设置初始值。
- 父组件值变化的时候会触发该变量的set方法父组件执行updateChildren方法对比新旧子组件并更新值。
到此这篇关于Vue中v-bind原理深入探究的文章就介绍到这了,更多相关Vue v-bind内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!