乐趣区

Vue原理Props-源码版

写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue 版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面 关注公众号 也可以吧

【Vue 原理】Props – 源码版

今天记录 Props 源码流程,哎,这东西,就算是研究过了,也真是会随着时间慢慢忘记的。

幸好我做了详细的文章,忘记了什么的,回忆起来必然是很快的。

好的,回到正题,Props

请你在读这篇之前,先去看看我的白话版

【Vue 原理】Props – 白话版

在上面这篇文章中,也已经清楚地解决了一个问题

父组件 如何 把数据 当做 props 传给子组件

所以今天,我们只需记录 Props 的处理流程源码即可


初始化

在创建 Vue 实例的过程中,会调用 initState 处理 options,比如 props,computed,watch 等

只要你 new Vue 创建实例之后,很快就会处理 options

function Vue(){
    ... 其他处理
    initState(this)

    ... 解析模板,生成 DOM 插入页面

}

function initState(vm) {    

    var opts = vm.$options;    

    if (opts.props) {initProps(vm, opts.props);
    }

    ... 处理 computed,watch,methods 等其他 options

}

initProps

你看到处理 Props,主要用到了一个方法 initProps,他就是本场的焦点了,让我们来采访下源码本码

function initProps(vm, propsOpt) {    
   // 这是父组件给子组件传入的 props 的具体值

   var propsData = vm.$options.propsData || {};    

   var props = vm._props = {};    

   for (var key in propsOpt){        

       // 给 props 的 key 设置 响应式

       defineReactive(props, key,  propsData[key]);        

       if (! (key in vm)) {            

           // 转接访问,访问 vm 属性,转到访问 vm._props 属性
           proxy(vm, "_props", key);
       }
   }
}

上面的代码主要做了三件事

1、遍历 props

2、给 props 设置响应式

3、给 props 设置代理

我们主要讲两件事

1、给 props 设置响应式

defineReactive(props, key,  propsData[key])

defineReactive 在这里就不给太多源码了,你只需要记住他就是给 props 设置响应式的

function defineReactive(obj, key) {    

    Object.defineProperty(obj, key, {get() {... 依赖收集},
        set(newVal) {.... 依赖更新}
    });
}

如果你想了解响应式,就可以看我这篇文章

【Vue 原理】响应式原理 – 白话版

Props 设置响应式,也是旨在数据改变时动态更新。

怎么设置响应式吗?看这里

【Vue 原理】依赖收集 – 源码版之基本数据类型

【Vue 原理】依赖收集 – 源码版之引用数据类型

数据是直接从 父组件上传过来的,没有进行拷贝等处理,原样传过来

怎么传的?也可以看

【Vue 原理】Props – 白话版

如果 props 是基本类型

在 子组件实例上设置这个 props 属性为响应式,跟 data 本质一样,作用是监听 props 修改

如果 props 是对象

也会在 子组件实例上 设置这个 props 属性为响应式,作用也是监听 props 修改

但是!

【不会递归对象】给对象内所有属性设置响应式,因为该对象【已经在父组件中】完成响应式设置了

也就是说

如果你在 子组件中直接修改 props 对象内的数据,父组件也会跟着修改

在记录的途中,我发现了一个问题,发现没有想象中的那么简单,所以现在郑重记录

当 父组件数据 改变,子组件怎么更新?

分类型的,说得比较详细,可能有点绕?

1、如果是基本类型,是这个流程

父组件数据改变,只会把新的数据传给子组件

子组件拿到新数据,就会直接替换到原来的 props

替换就是直接等哈,看下源码,重要语句标红

updateChildComponent 是子组件内部更新时会调用到的一个函数, 这是其中更新 props 的一个片段

function updateChildComponent(vm, propsData) {if (propsData && vm.$options.props) {        

      // 保存 props 的地方,用于访问转接,具体看文章下面

      var props = vm._props;        

      // 所有子组件上设置的 props 的 key

      var propKeys = vm.$options._propKeys || [];        

      for (var i = 0; i < propKeys.length; i++) {var key = propKeys[i];

        props[key] = propsData[key]
      }
     vm.$options.propsData = propsData;
   }
}

而 props 在子组件中也是响应式的,【直接 等号 替换】导致触发 set,set 再通知 子组件完成更新

数据是 基本类型,然后设置定时器修改数据

watcher1 是父组件,watcher2 是子组件

父组件内的 data num 通知 watcher1 更新
子组件内的 props child_num 通知 watcher2 更新

2、如果是对象,是这个流程

条件

父组件传 对象 给 子组件,并且父子组件 页面都使用到了这个数据

结果

那么这个对象,会收集到 父子组件的 watcher

所以

当 对象内部被修改的时候,会通知到 父和子 更新。

例子

父组件设置 obj 对象,并传给子组件


定时修改父组件数据 obj.name,可以看到是 obj.name 通知 父子更新

当然,如果对象被整个替换了,而不是修改内部,那么跟 基本类型一样

区别是什么?

1、基本类型是,子组件内部 props 通知 子组件更新的

2、引用类型是,父组件的数据 data 通知 子组件更新的

2、给 props 设置代理

在白话版中,我已经说得很清楚了,Props 有个移花接木的暗箱操作,就是访问转移

Data 也是这么做的

[
【Vue 原理】代理 Data – 源码版 ](https://mp.weixin.qq.com/s?__…)

你在项目中,会使用 http://this.xxx 去访问 props,props 已经当成了 实例的属性,所以可以直接访问

但是其实你访问的是【this._props.xxx】

为什么 Vue 要这么弄,目的就是为了方便开发啊,让我们直接简短了相关代码

而 React,访问 props,还要 this.props.xxxx,写这么长,不嫌麻烦吗?

那么,是怎么设置代理的呢,就是下面这行

proxy(vm, "_props", key);

proxy 是什么也不要急,瓜就在下面,拿凳坐好

function proxy(target, sourceKey, key) {    

  Object.defineProperty(target, key, {get() {return this[sourceKey][key]

      },

      set(val) {this[sourceKey][key] = val;

      }
  });
}

这段代码做了 2 个事

1、使用 props 在 vm 上占位,使得可以通过 http://vm.xxx 的形式访问到 props

2、设置 [Object.defineProperty] 的 get 和 set,间接获取和赋值 vm._props

所有访问赋值 props,转接到 vm._props 上,直观如下图


上个实例,方便大家看

说完收工

退出移动版