【Vue原理】Props - 源码版
写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
今天记录 Props 源码流程,哎,这东西,就算是研究过了,也真是会随着时间慢慢忘记的。
幸好我做了详细的文章,忘记了什么的,回忆起来必然是很快的。
好的,回到正题,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) { ....依赖更新 } }); }
如果你想了解响应式,就可以看我这篇文章
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 上,直观如下图
上个实例,方便大家看
说完收工