• 从源码看vue(v2.7.10)中的v-bind的原理


    前面我们分析了v-model的原理,接下来我们看看v-bind的实现又是怎样的呢?

    前置内容

    
    
    
    ...
    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    解析模板

    // 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
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以看出v-bind: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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    _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();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    主要执行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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    创建的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;
        }
       ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    由于第一个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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    该方法执行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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    实例化过程中使用了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);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    到这里为止从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);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    首先执行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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述

    获取到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);
        }
        ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    至此整个过程结束。

    总结

    1. 会将v-bind:propTest="a"解析成attrs: { propTest: _vm.a },并在渲染组件test的时候把该值传过去。
    2. 在实例化组件的时候会根据props里面创建的值和传进来的值做类型校验,然后并设置响应式同时设置初始值。
    3. 父组件值变化的时候会触发该变量的set方法父组件执行updateChildren方法对比新旧子组件并更新值。
  • 相关阅读:
    【Ubuntu】Systemctl控制nacos启动与关闭
    activiti框架搭建及问题记录
    postman 密码rsa加密登录-1获取公钥
    智能服务机器人产品及解决方案
    java spring cloud 企业工程管理系统源码+二次开发+定制化服务
    什么是响应式对象?响应式对象的创建过程?
    北戴河出游攻略
    2023年中国辣椒红素产量、需求量及行业市场规模分析[图]
    Linux 环境删除Conda
    1.5-36:计算多项式的值
  • 原文地址:https://blog.csdn.net/qq_35094120/article/details/127442146