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

(0)

相关推荐

  • vue指令 v-bind的使用和注意需要注意的点

    目录 1.v-bind:可以为元素的属性绑定一些数据 2.v-bind:可以简写成 : 推荐直接写冒号 3.v-bind:指令表达式的拼接, 1.v-bind:可以为元素的属性绑定一些数据 <div id="app"> <p v-bind:title="message" v-bind:id="pId">我是p标签</p> </div> <script src="./js/vue.js

  • vue实现用v-bind给src和href赋值

    目录 v-bind给src和href赋值 示例 v-bind:src无效问题 附上代码 v-bind给src和href赋值 用v-bind给src和href赋值其实很简单,即 v-bind:属性名="name",其中name就是data里json数据的键值,其简写形式为 :属性名="name". 示例  <div id="app">         <a v-bind:href="link" rel=&quo

  • 详解vue中v-model和v-bind绑定数据的异同

    vue的模板采用DOM模板,也就是说它的模板可以当做DOM节点运行,在浏览器下不报错,绑定数据有三种方式,一种是插值,也就是{{name}}的形式,一种是v-bind,还有一种是v-model.{{name}}的形式比较好理解,就是以文本的形式和实例data中对应的属性进行绑定.比如: var app = new Vue({ el: '#app', template: '<div @click="toggleName">{{name}}</div>', data

  • Vue 中指令v-bind动态绑定及与v-for结合使用详解

    目录 前言: 一. v-bind动态绑定class 1. v-bind动态绑定class(对象语法) 2. v-bind动态绑定class(数组用法) 3.v-bind动态绑定style(对象语法) 4.v-bind动态绑定style(数组语法) 二.v-bind和v-for的结合使用 前言: 在昨天的文章中已经基本介绍了,v-bind的基本使用,可以参考学习,本文是更加具体的解释v-bind的使用,和v-for结合的使用. 一. v-bind动态绑定class 1. v-bind动态绑定cla

  • Vue.js中v-bind指令的用法介绍

    一.什么是v-bind指令 v-bind指令用于响应更新HTML特性,允许将一个或多个属性动态绑定到表达式.v-bind是应用在动态属性上面的. 二.语法 v-bind语法如下: v-bind:动态属性名称="变量" 也可以简写成下面的形式: :动态属性名称="变量" 代码示例如下: <img :src="imgUrl" :title="title" /> 这里的src和title都是<img>标签的属

  • Vue项目中v-bind动态绑定src路径不成功问题及解决

    目录 v-bind动态绑定src路径不成功 解决方案 1 解决方案 2 解决方案 3 vue踩坑--动态v-for图片路径问题 问题所在 解决办法 实现代码 v-bind动态绑定src路径不成功 问题:在做Vue项目的时候,由于项目需求,需要动态绑定img的src时,突然发现如果说是直接请求后台接口的图片地址就能显示, 但是直接动态绑定img的src的图片的相对路径或者是绝对路径的时候,图片不能显示. 解决方案 1 当在给数据MyimgSrc 设置绝对路径或者是相对路径时应该使用require引

  • 带你理解vue中的v-bind

    目录 一.v-bind关键源码分析 1.v-bind化的属性统一存储在哪里:attrsMap与attrsList 2.解析HTML,解析出属性集合attrs,在start回调中返回 3.在start回调中创建ASTElement,createASTElement(... ,attrs, ...) 4.创建后ASTElement会生成attrsList和attrsMap 5.attrs的数据类型定义 6.绑定属性获取函数 二.如何获取v-bind的值 1.v-bind:key源码分析 2.v-bi

  • 详解Vue中的MVVM原理和实现方法

    下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家可以学习到: 1.Vue数据双向绑定核心代码模块以及实现原理 2.订阅者-发布者模式是如何做到让数据驱动视图.视图驱动数据再驱动视图 3.如何对元素节点上的指令进行解析并且关联订阅者实现视图更新 一.思路整理 实现的流程图: 我们要实现一个类MVVM简单版本的Vue框架,就需要实现一下几点: 1.实现一个数据监听Observer,对数据对象的所有属性进行监听,数据发生变化可以获取到最新值通知订阅者. 2.实现一个解析器Compi

  • vue中的数据绑定原理的实现

    本文主要介绍了vue中的数据绑定原理的实现,分享给大家,也给自己留个笔记,具体如下: vue中的响应式数据绑定是通过数据劫持和观察者模式来实现的.当前学习源码为vue2.0 源码关键目录 src |---core | |---instance | |---init.js | |---state.js | |---observer | |---dep.js | |---watcher.js 当我们实例化一个vue应用的时候,会伴随着各种的初始化工作,相关的初始化工作代码在init.js文件中 //

  • vue学习笔记之Vue中css动画原理简单示例

    本文实例讲述了Vue中css动画原理.分享给大家供大家参考,具体如下: 当transition包裹了一个元素之后,vue会自动分析元素的css样式,构建动画流程. so,我们需要定义style. vue中的css动画,其实就是某一个时间点,给元素再增加了一个css样式体现的. v-if.v-show.动态组件 都可以实现过渡效果. 如果没有给transition定义name,vue中默认是.v-enter..v-leave-to. <!DOCTYPE html> <html lang=&

  • vue中的v-model原理,与组件自定义v-model详解

    VUE中的v-model可以实现双向绑定,但是原理是什么呢?往下看看吧 根据官方文档的解释,v-model其实是一个语法糖,它会自动的在元素或者组件上面解析为 :value="" 和 @input="", 就像下面这样 // 标准写法 <input v-model="name"> // 等价于 <input :value="name" @input="name = $event.target.val

  • 深入了解Vue中双向数据绑定原理

    目录 数据的变化反应到视图 命令式操作视图 声明式操作视图 小结 视图的变化反应到数据 现存的问题 数据的变化反应到视图 前面我们了解到数据劫持之后,我们可以在数据发生修改之后做任何我们想要做的事情,操作视图当然也是OK的 命令式操作视图 目标:我们通过原始的操作dom的方式让每一次的name的最新值都能显示到p元素内部 <div id="app"> <p></p> </div> <script> let data = { n

  • Vue中CSS动画原理的实现

    下面这段代码,是点击按钮实现hello world显示与隐藏 <div id="root"> <div v-if="show">hello world</div> <button @click="handleClick">按钮</button> </div> let vm = new Vue({ el: '#root', data: { show:true }, method

  • 详解vue 中 scoped 样式作用域的规则

    哈喽!大家好!我是木瓜太香,今天我们来聊一个 vue 的样式作用域的问题,通常我们开发项目的时候是要在 style 上加上 scoped 来起到规定组件作用域的效果的,所以了解他们的规则也是很有必要的,可以让你更清晰的了解你的项目样式是怎么运作的. 先来说说实现方式 vue中的样式作用域是通过属性选择器来实现的,例如同样一个类名,我们是通过 .类名[属性名] 来做区分的,我们这里主要是要搞清楚这里的属性名是怎么分配的. 样式作用域规则 接下来我们分情况来说一下样式作用域: 对于有样式作用域的组件

  • Vue中v-bind原理深入探究

    目录 前置内容 解析模板 总结 前面我们分析了v-model的原理,接下来我们看看v-bind的实现又是怎样的呢? 前置内容 <template> <div> <test :propTest="a"></test> <div @click="changeA">点我</div> </div> </template> <script> import test fr

  • 八个Vue中常用的v指令详解

    目录 Vue中常用的8种v指令 1 v-text 指令 2 v-html 指令 3 v-on 指令 案例:计数器 4 v-show 指令 5 v-if 指令 6 v-bind 指令 7 v-for 指令 8 v-on 补充 总结 Vue中常用的8种v指令 根据官网的介绍,指令 是带有 v- 前缀的特殊属性.通过指令来操作DOM元素 指令 功能 v-text=“变量/表达式” 文本的设置字符串变量+数字可以直接写是拼接字符串如果出现要使用外部不相同的引号 v-html=“变量” 文本或者页面的设置

  • Vue双向数据绑定与响应式原理深入探究

    目录 一.双向数据绑定和数据响应式是相同的吗 二.双向数据绑定的原理 三.数据响应式的原理与实现 一.双向数据绑定和数据响应式是相同的吗 不相同,原因如下: 响应式是指通过数据区驱动DOM视图的变化,是单向的过程. 双向数据绑定就是无论用户更新View还是Model,另一个都能跟着自动更新. 例如:当用户填写表单时,View的状态就被更新了,如果此时可以自动更新Model的状态,那就相当于我们把Model和View做了双向绑定. 双向数据绑定的数据和DOM是一个双向的关系. 响应式是双向绑定的一

随机推荐