vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue v-bind

Vue中v-bind原理深入探究

作者: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);
    }
    ...
};

至此整个过程结束。

总结

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

您可能感兴趣的文章:
阅读全文