Vue v-bind
Young soul2 人气:0前面我们分析了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方法对比新旧子组件并更新值。
加载全部内容