文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Vue中v-bind原理深入探究

2022-11-13 18:41

关注

前面我们分析了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 );
        }
        ...
    }
}
...
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);
       
       {
           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 );
    }
    else {
        // updates
        vm.$el = vm.__patch__(prevVnode, vnode);
    }
    ...
};

至此整个过程结束。

总结

到此这篇关于Vue中v-bind原理深入探究的文章就介绍到这了,更多相关Vue v-bind内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯