学习vue源码(19)四探生命周期之初始化props

本文详细解释了Vue.js中props的传递机制,以及组件初始化过程中的props、methods、data、computed和watch的顺序。重点介绍了父组件如何向子组件传递props,以及子组件如何接收和管理这些数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

if(opts.methods) initMethods(vm,opts.methods);

if(opts.data){

initData(vm);

}else{

observe(vm._data = {},true/asRootData/);

}

if(opts.computed) initComputed(vm,opts.computed);

if(opts.watch && opts.watch !== nativeWatch){

initWatch(vm,opts.watch);

}

1、首先在vm上新增一个属性_watchers,用来保存当前组件中所有的watcher实例。无论是使用vm.$watch注册的watcher还是使用watch选项添加的watcher实例,都会添加到vm._watchers中。

2、可以通过vm._watchers得到当前Vue.js实例中所注册的所有watcher实例,并将它们一次卸载。

3、用户在实例化Vue.js时使用了哪些状态,哪些状态就需要被初始化,没有用到的状态则不用初始化。例如,用户只使用了data,那么只需要初始化data即可。

4、初始化的顺序其实是精心安排的。先初始化props,后初始化data,这样就可以在data中使用props中的数据了。在watch中既可以观察props,也可以观察data,因为它是最后被初始化的。

5、初始化状态可以分为5个子项,分别是初始化props、初始化methods、初始化data、初始化computed和初始化watch。

初始化props


开始之前,我提出问题

1、父组件 怎么传值给 子组件的 props

2、子组件如何读取props

这一节,我们带着问题去开始我们的讲解

明白这三个问题,那么相信你也就理解了 props 的工作原理

场景设置

现在我们有一个这样的 根组件 A 和 他的 子组件 testb

根组件A 会 把 parentName 传给 子组件 testb 的 props

根组件A 也是 组件testb 的 父组件

new Vue({

el:“.a”,

name:“A”,

components:{

testb:{

props:{

childName:“”

},

template: ‘

父组件传入的 props 的值 {{childName}}

’,

}

},

data(){

return {

parentName:“我是父组件”

}

},

})

按照上面的例子,开始我们的问题解析

父组件怎么传值给子组件的 props

这部分内容是 props 的重中之重,必须理解

1 props 传值的设置

根据上面的场景设置,testb 是一个子组件,接收一个 props(child-name)

然后 根组件 A 把 自身的 parentName 绑定到 子组件的属性 child-name 上

2 props 父传子前

父组件的模板 会被解析成一个 模板渲染函数

(function() {

with(this){

return _c(‘div’,{staticClass:“a”},[

_c(‘testb’,{attrs:{“child-name”:parentName}})

],1)

}

})

这段代码需要解释下

其实,这是属于代码生成器的部分,感兴趣的可以看学习vue源码(9)手写代码生成器

1、_c 是渲染组件的函数,_c(‘testb’) 表示渲染 testb 这个子组件

2、因为 with 的作用是,绑定大括号内代码的 变量访问作用域

3、这是一个匿名自执行函数,会在后面执行

简化上面的函数,做个例子测试一下

3 .props 开始赋值

之后,模板函数会被执行,执行时会绑定 父组件为作用域

所以渲染函数内部所有的变量,都会从父组件对象 上去获取

绑定了父作用域之后, parentName 自然会从父组件获取,类似这样

{ attrs: { child-name: parentVm.parentName } }

函数执行了,内部的 _c(‘testb’) 第一个执行,然后传入了 赋值后的 attrs

父组件赋值之后的 attrs 就是下面这样

{ attrs: { child-name: “我是父组件” } }

此时,父组件就正式 利用 props 把 parentName 传给了 子组件的props child-name

4 .子组件保存 props

_c(‘testb’,{attrs:{“child-name”:parentName}})

子组件拿到父组件赋值过后的 attr

而 attrs 包含 普通属性 和 props,所以需要 筛选出 props,然后保存起来

5 .子组件 设置响应式 props

props 会被 保存到 实例的 _props 中,并且 会逐一复制到 实例上,并且每一个属性会被设置为 响应式 的

你看到的,每一个 实例都会有 一个 _props 的同时,也会把属性直接放在 实例上。

对于props还有个问题:就是我们写的时候可是是数组形式,也是对象形式,这是为什么呢?

  1. 数组形式

props: [‘name’, ‘value’]

  1. 对象形式

对象形式内部也提供了三种写法:

props: {

// 基础的类型检查

name: String,

// 多个可能的类型

value: [String, Number],

// 对象形式

id: {

type: Number,

required: true

}

}

其实 vue初始化时,会把props统一规格化成对象形式

function normalizeProps (options: Object, vm: ?Component) {

const props = options.props

if (!props) return

const res = {}

let i, val, name

if (Array.isArray(props)) {

} else if (isPlainObject(props)) {

} else if (process.env.NODE_ENV !== ‘production’) {

}

options.props = res

}

normalizeProps函数就是vue实际处理props的地方,从函数名的翻译我们可以看出该函数的功能就是标准化props的值。该函数主要分成3部分:① 从options对象中获取props的值并且定义一个res空对象;②几个if … else,分别根据props值的不同类型来处理res对象;③ 用处理后的res对象覆盖原来options对象的props属性的值。

接下来看看那几个if … else的代码:

if (Array.isArray(props)) {

i = props.length

while (i–) {

val = props[i]

if (typeof val === ‘string’) {

name = camelize(val)

res[name] = { type: null }

} else if (process.env.NODE_ENV !== ‘production’) {

warn(‘props must be strings when using array syntax.’)

}

}

}

这个代码实际就是处理props的值为数组的情况,例如:

props: [‘name’, ‘value’]

。使用while遍历该数组,如果数组内元素的类型不是字符串并且不是生产环境,那么就抛错:‘props的值类型为数组时,数组里面的元素的类型就必须是字符串’。如果是字符串的情况下,使用camelize函数处理一下val的值,并且赋值给name变量。这里的camelize函数的实际作用就是将’-'转换为驼峰。camelize函数具体的实现方式在后面分析。然后在res对象上面添加一个为name变量的属性,该属性的值为空对象 { type: null }。

props: [‘name’, ‘value’]

这种写法经过上面的处理后就会变成了下面这样:

props: {

name: {

type: null

},

value: {

type: null

}

}

接下来看看下面这个else if(isPlainObject(props)),这里的isPlainObject函数实际就是返回props的值是否为object,isPlainObject函数的具体实现我们也在后面分析。

else if (isPlainObject(props)) {

for (const key in props) {

val = props[key]

name = camelize(key)

res[name] = isPlainObject(val)

? val
{ type: val }

}

}

使用for…in遍历props对象,和上面一样使用camelize函数将’-'转换为驼峰。这里有个三目运算:

res[name] = isPlainObject(val) ? val : { type: val }

判断了一下val如果是object,那么在res对象上面添加一个为name变量的属性,并且将该属性的值设置为val。这个其实就是处理下面这种props的写法:

props: {

// 对象形式

id: {

type: Number,

required: true

}

}

如果val不是object,那么也在res对象上面添加一个为name变量的属性,并且将该属性的值设置为{ type: val }。这个其实就是处理下面这种props的写法:

props: {

// 基础的类型检查

name: String,

// 多个可能的类型

value: [String, Number],

}

经过处理后props会变成了下面这样:

props: {

name: {

type: String

},

value: {

type: [String, Number]

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可免费获取,包括答案解析。

效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-cw5cD3yd-1713778112326)]

[外链图片转存中…(img-eu3isxGN-1713778112326)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-wGeh88DM-1713778112327)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-ObXzYyA7-1713778112327)]

最后

文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可免费获取,包括答案解析。

[外链图片转存中…(img-B9gmgT5G-1713778112327)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值