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还有个问题:就是我们写的时候可是是数组形式,也是对象形式,这是为什么呢?
- 数组形式
props: [‘name’, ‘value’]
- 对象形式
对象形式内部也提供了三种写法:
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]
}
}
所以不管我们使用vue提供的props哪种写法,最终vue都会帮我们转换成下面这种类型:
props: {
name: {
…,
type: ‘类型’
}
}
明白了这些之后,看开头我们提出的initProps函数,就容易理解的多了
initProps
如果觉得有点难理解,可以先跟着代码后面的解释,逐行明白每一行代码的作用。
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
//
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
// 非生产环境下进行检查和提示
if (process.env.NODE_ENV !== ‘production’) {
//
const hyphenatedKey = hyphenate(key)
//
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.
,
vm
)
}
defineReactive(props, key, value, () => {
if (vm.$parent && !isUpdatingChildComponent) {
warn(
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: "${key}"
,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
if (!(key in vm)) {
proxy(vm, _props
, key)
}
}
// 开启观察状态标识
toggleObserving(true)
}
-
接收vm,propsOptions两个参数,propsOptions是我们开发者写的组件里的props。
-
赋值propsData,propsData父组件传入的props
-
定义实例的_props私有属性,并赋值给props
-
key用来 缓存prop键,以便将来props更新可以使用Array而不是动态对象键枚举进行迭代。
-
isRoot表示是否是根实例,对于非根实例,关闭观察标识即toggleObserving(false)
-
遍历props配置对象,向缓存键值数组key中添加键名。
-
验证prop的值:validateProp执行对初始化定义的props的类型检查和默认赋值,如果有定义类型检查,布尔值没有默认值时会被赋予false,字符串默认undefined。意思就是如果你在子组件写的props的类型是布尔值的话例如:
props:{
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-exTHjhDH-1715232410553)]
[外链图片转存中…(img-CvY6aq63-1715232410554)]
[外链图片转存中…(img-B89Tc1Hq-1715232410554)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!