您现在的位置是:网站首页> 编程资料编程资料
Vue3源码分析组件挂载初始化props与slots_vue.js_
2023-05-24
727人已围观
简介 Vue3源码分析组件挂载初始化props与slots_vue.js_
前情提要
- 上文我们分析了挂载组件主要调用了三个函数: createComponentInstance(创建组件实例)、setupComponent(初始化组件)、setupRenderEffect(更新副作用)。并且上一节中我们已经详细讲解了组件实例上的所有属性,还包括emit、provide等的实现。本文我们将继续介绍组件挂载流程中的初始化组件。
本文主要内容
- 初始化props和slots的主要流程。
- 如何将传递给组件的属性分发给
props和attrs(需要被透传的属性)。 - 用户自己实现了
render函数,如何对其进行标准化。 - 标准的插槽需要满足哪些条件。
初始化组件
(1).setupComponent
setupComponent: 这个函数主要用于初始化组件。内部主要调用了initProps、initSlot、对于有状态组件还需要调用setupStatefulComponent。
function setupComponent(instance) { //获取vnode的props(真正传递的props) const { props, children } = instance.vnode; //判断当前是否是有状态组件组件 const isStateful = isStatefulComponent(instance); //通过传递的真实props和声明的props 分离组件参数 //组件参数放入props中 其余放入instance.attrs //处理了props的default情况等 initProps(instance, props, isStateful); //初始化插槽 initSlots(instance, children); //验证名称是否合法,components中的组件名称是否 //合法,代理instance.ctx,创建setup函数的ctx,调用setup函数 //处理得到的结果 const setupResult = isStateful ? setupStatefulComponent(instance) : undefined; return setupResult; } isStatefulComponent: 这个主要用于判断是否是有状态组件、还记得Vue3源码分析(4)中提到的ShapeFlag吗?我们在createVNode中会判断type的类型、然后设置shapeFlag来标识当前创建的虚拟节点类型。因此我们只需要获取组件的vNode、而vNode中有shapeFlag然后判断他的值,就知道他是不是有状态组件了。
function isStatefulComponent(instance) { return instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT; } (2).initProps
initProps: 在创建组件实例中,我们只对propsOptions做了处理、但是props和attrs目前都还是null、所以我们需要区分出来那些是props那些是attrs,同时有些propsOptions中设置了default属性,那么我们还需要判断是否传递了这个属性,如果没有传递那么应该用default属性中的值、又比如传递了并且声明了props:{yes:Boolean},那么应该将 yes的值变为true。而这些就是在初始化props的时候完成的。
function initProps(instance, rawProps, isStateful) { //定义需要放入的 const props = {}; const attrs = {}; //attrs.__vInternal = 1 shared.def(attrs, InternalObjectKey, 1); //创建propsDefaults instance.propsDefaults = Object.create(null); //将真实传递的props分配给instance的props和attrs setFullProps(instance, rawProps, props, attrs); //遍历normalized(合并和的props) for (const key in instance.propsOptions[0]) { if (!(key in props)) { props[key] = undefined; } } //最后将分配好的props和attrs赋值到instance if (isStateful) { instance.props = reactivity.shallowReactive(props); } else { //不存在type.props则让props为attrs if (!instance.type.props) { instance.props = attrs; } else { instance.props = props; } } instance.attrs = attrs; } setFullProps: 在Vue3源码分析(5)中我们详细讲解了propsOptions,如果读到这里还是不理解的小伙伴可以跳到上一章再去看看。首先重propsOptions中解构到options和needCastKeys(需要特殊处理的key)。options就是进行标准化后的组件定义的props。
- 遍历真正传递给组件的
props,拿到key去options中寻找,如果找到了,表示这个属性是组件需要接受的props,进一步判断是否是需要特殊处理的key如果不是就可以放入props中。 - 如果是需要特殊处理的key,获取他的值放入
rawCastValues当中。如果在options中没有找到,就判断一下emitsOptions中是否有,如果这里面也没有那就可以放入attrs中,attrs就是需要透传到subTree上的属性。 - 最后遍历需要特殊处理的
key调用resolvePropValue对props进行最后的处理。
function setFullProps(instance, rawProps, props, attrs) { //获取通过mixins和extends合并的props const [options, needCastKeys] = instance.propsOptions; let hasAttrsChanged = false; //attrs是否发生改变 let rawCastValues; if (rawProps) { for (let key in rawProps) { //如果key是"ref" "key" "ref_for" "ref_key" //"onVnodeBeforeMount" "onVnodeMounted" //"onVnodeBeforeUpdate "onVnodeUpdated" //"onVnodeBeforeUnmount" "onVnodeUnmounted" //那么就跳过 if (shared.isReservedProp(key)) { continue; } //获取rawProps:{a:1}=>value=1 const value = rawProps[key]; let camelKey; //小驼峰式的key if ( options && shared.hasOwn(options, (camelKey = shared.camelize(key))) ) { //这个key不是含有default属性的 if (!needCastKeys || !needCastKeys.includes(camelKey)) { props[camelKey] = value; } //props:{"msg":{default:"a"}} //含有default属性的放入rawCastValues中 else { (rawCastValues || (rawCastValues = {}))[camelKey] = value; } } //判断当前的key是否是用于emits的 else if (!isEmitListener(instance.emitsOptions, key)) { //不是emit自定义事件的key也不是组件参数那么就是attrs if (!(key in attrs) || value !== attrs[key]) { attrs[key] = value; hasAttrsChanged = true; } } } } /** * * 这里涉及到四个属性instance, rawProps, props, attrs * instance:是当前组件的实例 * rawProps:真正传递的props可能含有组件参数props, * 标签属性attrs,自定义emit事件 * props:代表声明并且接受到的props * attrs:代表没有声明props也不属于emits属性的属性 * needCastKeys:代表需要特殊处理的属性 * 例如props:{msg:{default:"a"}}那么msg会被放入 * needCastKeys中 * */ if (needCastKeys) { //获取非响应式的props const rawCurrentProps = reactivity.toRaw(props); const castValues = rawCastValues || {}; for (let i = 0; i < needCastKeys.length; i++) { const key = needCastKeys[i]; //msg //对于有default的属性进行重设 //props:{msg:{default:"a"}} props[key] = resolvePropValue( options, //合并mixins和extends后的props(定义方) rawCurrentProps, //非响应式的props(接受方) key, //(含有default)的key "msg" //例如传递了"msg":1 定义了:props:{msg:{default:"a"}} //castValues[key]=1 castValues[key], instance, //实例 !shared.hasOwn(castValues, key) ); } } return hasAttrsChanged; } resolvePropValue: 对特殊的key进行处理。
- 首先从
opt中判断是否有default属性,如果有default属性而且传递的value是undefined的话表示需要使用默认值,还需要进一步判断,如果传递的不是函数但是声明的是函数,需要将value设置为这个函数的返回值。例如:props:{yes:Number,default:(props)=>{}}并且没有向组件传递yes这个参数,那么yes的值将会是default函数的返回值。 - 对于
propsOptions中定义的接受值类型是Boolean的,但是又没有传递且没有默认值则设置这个值为false。 - 当然还有
并且声明了是 Boolean,则会设置为true。
function resolvePropValue(options, props, key, value, instance, isAbsent) { //获取{msg:{default:"a"}}中的{default:"a"} const opt = options[key]; if (opt != null) { //判断是否有default属性 const hasDefault = shared.hasOwn(opt, "default"); //如果定义了default但是没有接受到value值 if (hasDefault && value === undefined) { const defaultValue = opt.default; //如果需要接受的类型不是函数,但是接受到了函数 //看看实例的propsDefaults是否有当前key的值 //还是没有则调用这个defaultValue函数取得值 if (opt.type !== Function && shared.isFunction(defaultValue)) { const { propsDefaults } = instance; if (key in propsDefaults) { value = propsDefaults[key]; } else { //包裹是为了在调用这个函数的时候 //获取当前实例不会出错 setCurrentInstance(instance); value = propsDefaults[key] = defaultValue.call(null, props); unsetCurrentInstance(); } } //设置为默认值 else { value = defaultValue; } } //需要接受的类型是Boolean if (opt[0]) { //没有设置默认值,也没有传递这个值则为false if (isAbsent && !hasDefault) { value = false; } // 并且声明了yes则设置为true else if (opt[1] && value === "") { value = true; } } } return value; } (3).initSlots
initSlots:还记得在Vue3源码分析(4)中我们详细讲解了normalizeChildren,他主要用于标准化插槽,给vNode的shapeFlag加上ARRAY_CHILDREN或TEXT_CHILDREN或SLOTS_CHILDREN的标识,但是并没有添加到实例的slots属性上。因为那个时候还没有创建实例,所以我们只能在那时候打上标记,在创建实例之后,也就是现在,在去初始化slots。对于SLOTS_CHILDREN、TEXT_CHILDREN、ARRAY_CHILDREN分别是在那种情况下添加到shapeFlag上的,如果你不了解可能会影响这一段代码的阅读,建议在看看第四小节。因为间隔较远,所以理解起来很困难,这部分的文章主要是阐述整个Vue3的运行机制。我们后面的章节还会单独讲解slots的实现。
SLOTS_CHILDREN: 首先判断children._是否存在,如果是通过Vue的编译器得到的那么一定会有这个标识,当然,用户自己书写render函数也可以自己传递这个标识符。但是大部分用户是不会传递的,所以else分支中就是为了处理这种情况,而对于children._存在的,可以直接把children当做实例的slots属性。_标识有三个值STABLE、DYNAMIC、FORWORD这个在第四小节也已经讲过了,就不在重复了。TEXT_CHILDREN、ARRAY_CHILDREN: 因为children不是一个对象,而是数组或字符串或null,那么需要将其标准化为对象形式。调用normalizeVNodeSlots处理。
function initSlots(instance, children) { //判断当前实例的children是否是slots if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { const type = children._; //获取shapeSlots //有"_"标识表示是通过compiler编译得到的 if (type) { //如果有SLOTS_CHILDREN标识 表示children就是slots属性 instance.slots = reactivity.toRaw(children); //将_属性变为不可枚举属性 shared.def(children, "_", type); } else { /** * render(){ * return h(Comp,null,{ * default:()=>h("div",null), * header:()=>h("div",null) * }) * } * 没有则表示用户自己写了render函数 * 这个时候用户可能不会添加"_"属性 * 所以需要对slots进行标准化 */ normalizeObjectSlots(children, (instance.slots = {})); } } else { instance.slots = {}; //如果children为字符串或者null或数组情况 if (children) { normalizeVNodeSlots(instance, children); } } //标识slots为内部属性 shared.def(instance.slots, InternalObjectKey, 1); } - 我们先来看看到底要标准化成什么样子,其实对于
slots提示: 本文由神整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!
相关内容
- Vue3源码分析组件挂载创建虚拟节点_vue.js_
- Vue computed实现原理深入讲解_vue.js_
- Nodejs处理Json文件并将处理后的数据写入新文件中_node.js_
- Vue3 SFC 和 TSX 方式自定义组件实现 v-model的详细过程_vue.js_
- 详解Angular组件数据不能实时更新到视图上的问题_AngularJS_
- react组件的创建与更新实现流程详解_React_
- react源码层分析协调与调度_React_
- Node.js中http模块和导出共享问题_node.js_
- JS面试之console的异步性怎么理解详解_javascript技巧_
- react源码层深入刨析babel解析jsx实现_React_
