Vue中v-bind原理深入探究

 更新时间:2022年10月21日 16:13:23   作者:Young soul2  
这篇文章主要给大家分享了 v-bind的使用和注意需要注意的点,下面文章围绕 v-bind指令的相关资料展开内容且附上详细代码 需要的小伙伴可以参考一下,希望对大家有所帮助

前面我们分析了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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解如何用模块化的方式写vuejs

    详解如何用模块化的方式写vuejs

    这篇文章主要介绍了详解如何用模块化的方式写vuejs,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • 这15个Vue指令,让你的项目开发爽到爆

    这15个Vue指令,让你的项目开发爽到爆

    这篇文章主要介绍了这15个Vue指令,让你的项目开发爽到爆,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-10-10
  • vue+tsc+noEmit导致打包报TS类型错误问题及解决方法

    vue+tsc+noEmit导致打包报TS类型错误问题及解决方法

    当我们新建vue3项目,package.json文件会自动给我添加一些配置选项,这写选项基本没有问题,但是在实际操作过程中,当项目越来越复杂就会出现问题,本文给大家分享vue+tsc+noEmit导致打包报TS类型错误问题及解决方法,感兴趣的朋友一起看看吧
    2023-10-10
  • vue-cli中打包图片路径错误的解决方法

    vue-cli中打包图片路径错误的解决方法

    这篇文章主要介绍了vue-cli中打包图片路径错误的解决方法 ,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • Vue Router动态路由使用方法总结

    Vue Router动态路由使用方法总结

    这篇文章主要介绍了Vue Router动态路由使用方法总结,需要的朋友可以参考下
    2023-10-10
  • vue2封装input组件方式(输入的双向绑定)

    vue2封装input组件方式(输入的双向绑定)

    这篇文章主要介绍了vue2封装input组件方式(输入的双向绑定),具有很好的参考价值,希望对大家有所帮助。
    2023-04-04
  • vue3之Suspense加载异步数据的使用

    vue3之Suspense加载异步数据的使用

    本文主要介绍了vue3之Suspense加载异步数据的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • 基于mpvue搭建微信小程序项目框架的教程详解

    基于mpvue搭建微信小程序项目框架的教程详解

    mpvue从底层支持 Vue.js 语法和构建工具体系,同时再结合相关UI组件库,便可以高效的实现小程序开发。这篇文章主要介绍了基于mpvue搭建小程序项目框架 ,需要的朋友可以参考下
    2019-04-04
  • 浅谈Vue.js路由管理器 Vue Router

    浅谈Vue.js路由管理器 Vue Router

    这篇文章主要介绍了Vue.js路由管理器 Vue Router,主要介绍的是路由元信息,命名路由以及嵌套路由等使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-08-08
  • vue cli+axios踩坑记录+拦截器使用方式,代理跨域proxy

    vue cli+axios踩坑记录+拦截器使用方式,代理跨域proxy

    这篇文章主要介绍了vue cli+axios踩坑记录+拦截器使用方式,代理跨域proxy,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04

最新评论