写文章不容易,点个赞呗兄弟
专注 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 上,直观如下图
上个实例,方便大家看
说完收工