您现在的位置是:网站首页> 编程资料编程资料
Vue3组件挂载之创建组件实例详解_vue.js_
2023-05-24
431人已围观
简介 Vue3组件挂载之创建组件实例详解_vue.js_
前情提要
上文我们讲解了执行createApp(App).mount('#root')中的mount函数,我们分析了创建虚拟节点的几个方法,以及setRef的执行机制、本文我们继续讲解mountComponent,挂载组件的流程。
本文主要内容
createComponentInstance发生了什么?- 如何标准化组件定义的
props、emits? - 为什么
provide提供的值子组件都能访问到? 组件的v-model实现原理、组件v-model修饰符实现原理。- 组件
emit实现原理。 once修饰符实现原理。- 几乎
所有组件实例属性的详细讲解。
mountComponent
- 这个方法主要用于
挂载组件,根据传递的虚拟节点创建组件实例,调用setupComponent函数进行初始化组件实例的插槽和props,如果是有状态组件还需要处理setup的返回值。最后调用setupRenderEffect绑定副作用更新函数。这个函数比较简单,我们将重心放到createComponentInstance、setupComponent、setupRenderEffect当中、后文也是围绕这三个方法进行依次讲解。
const mountComponent = ( initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) => { //创建组件实例 const instance = (initialVNode.component = createComponentInstance( initialVNode, parentComponent, parentSuspense )); setupComponent(instance); if (instance.asyncDep) { //处理异步逻辑,suspense的时候在进行讲解 } setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ); }; 创建组件实例
createComponentInstance: 根据虚拟节点创建组件实例的方法,简单的说就是创建一个instance对象,这个上面有许多的属性用来描述当前这个组件以及存储需要后续使用到的属性,所以我们主要讲解各个属性的作用,我们先来看看这个函数的源码。uid: 标识当前实例的id,这是一个从0开始的递增值,同时他也是Vue调度器的优先级值,越小的值优先级越高,试想一下,父组件先挂载,然后才会挂载子节点,子节点如果包含组件,那么uid的值将会比父组件的uid值大,当父组件挂载完毕,父组件的兄弟组件开始挂载,那么uid将会更大,这样也符合流程更新逻辑,关于调度器我们之后单独开一章节来进行讲解。vnode: 实例的虚拟节点。parent: 当前组件实例的父组件实例。appContext:createApp的时候注入的一个上下文,我们通过app.component,app.mixin调用后缓存的component和mixin都放在这里面,如果没有parent表示当前是根组件,那么就是这个appContext,如果有parent则继承parent的appContext。我们可以通过这里发现所有的组件的appContext都会是唯一的一个context。因此所有组件实例都将能访问到全局注册的component、mixin、directive等。我们来回顾一下它的结构(省略了部分属性)。
{ app: null, config: { globalProperties: {}, optionMergeStrategies: {}, compilerOptions: {}, }, mixins: [], components: {}, directives: {}, }; next:组件更新有两种形式,一种是组件内部的状态发生了改变,引起了组件自身更新;另外一种是父组件传递了props,props改变,父组件更新、导致子组件引起了更新。第一种next是null。如果是第二种,那么需要给next赋值为当前子组件最新的VNode(虚拟节点)。而next将会用于更新组件的props以及slots等属性。组件自身状态改变引起的更新是不需要更新props的。subTree: 调用render函数之后返回的组件要渲染的虚拟根节点,可以通过这个属性读取到组件需要渲染的所有虚拟节点。effect: 在setupRenderEffect中创建的reactiveEffect,我们在Vue3源码分析(2)中讲解了reactiveEffect,不了解的可以在阅读一下,目前简单理解为在调用render函数的时候收集依赖,如果响应式的值发生了改变会重新调用update函数执行更新流程。我们将会在讲解setupRenderEffect中详细讲解这一部分。update:组件更新函数,只要调用了这个函数,组件就会强制更新。同时响应式发生改变调用的也是这个函数。render:渲染函数。可以通过编译得到,也可以通过写template属性获得,也可以通过自己写render函数。exposed: 组件指定的暴露到外部的属性。provides: 用户在组件中设置了provide,子代组件可以通过inject收到传递的值,provides:parent ? parent.provides : Object.create(appContext.provides)通过这段代码分析我们可以知道,provides中至少含有全局提供的provides,如果当前组件提供了provide,后面会将其混合,并且继承自父组件的provides,这也就解释了为什么provides可以向下传递,因为每一层都可以收到本组件的provides和父组件的provides并进行合并。components: 如果你想在template中使用组件,需要在这里注册,对于使用了语法糖的会将其编译到components属性当中,当然这个属性就是用来存放这些注册的组件的。directives: 存放自定义指令。propsOptions:合并了mixins和extends后的props,这里的属性为什么是propsOptions而不是props呢?这个VNode中的props又有什么关系呢?实际上propsOptions代表的是用户在组件内定义的props,而VNode中的props是外部传递给组件的props。这一点要加以区分。同时这里调用了normalizePropsOptions来对propsOptions进行标准化。接下来我们分析一下normalizePropsOptions函数。这个函数比较长我们分成三个部分来分析。
- 首先从
appContext中获取props缓存,避免处理过了组件props重复处理。如果comp不是一个函数,这个判断是因为,Vue3中允许函数组件的存在,但是函数组件是无状态组件也没有props,所以不做处理。那么剩下的都是有状态组件了,这里会处理全局注册的mixins,组件本身的mixins和extends。我们可以发现全局的最先处理,所以全局注册的mixins的优先级将会是最低的,其次是extends,显然优先级最高的则是组件自身的mixins,因为他最后执行,那么shared.extend将会最后覆盖之前的props。我们还可以发现extends属性和mixins属性在实现上没有任何区别,只是mixins可以是数组,而extends不能是数组。最后说一下asMixin,我们知道全局的mixins只需要合并一次,但是normalizePropsOptions会调用多次,为了避免全局属性混合的多次执行,设置了asMixin这个参数。当asMixin为true的时候表示不需要在合并全局的mixins了。特别提示:shared.extend就是Object.assign。
function normalizePropsOptions(comp, appContext, asMixin = false) { //获取props的缓存 const cache = appContext.propsCache; const cached = cache.get(comp); //这个缓存是一个type对应一个[normalized, needCastKeys] //normalized表示合并了mixins和extends后的props if (cached) { return cached; } const raw = comp.props; const normalized = {}; const needCastKeys = []; let hasExtends = false; if (!shared.isFunction(comp)) { //用于合并props的函数,因为extends和mixins //中还可以写mixins和extends所以需要递归合并 /** * 例如const mixins = [{ * extends:{}, * mixins:[{props}], * props * }] */ const extendProps = (raw) => { hasExtends = true; const [props, keys] = normalizePropsOptions(raw, appContext, true); //normalized为合并后的props shared.extend(normalized, props); if (keys) needCastKeys.push(...keys); }; //首先合并全局注册的mixins中的props属性(最先合并的优先级最低) if (!asMixin && appContext.mixins.length) { appContext.mixins.forEach(extendProps); } //然后合并extends属性(中间合并的优先级居中) //(extends功能与mixins几乎一样)但是更注重于继承 //并且extends不能是数组 if (comp.extends) { extendProps(comp.extends); } //最后合并组件自身的mixins(最后合并的优先级最高) if (comp.mixins) { comp.mixins.forEach(extendProps); } } //省略第二部分的代码... } - 我们知道这个函数主要是对
propsOptions进行标准化,简单的说就是将各式各样的propsOptions统一成唯一标准。当传递的props:['msg']数组形式的时候,我们需要将其转化为props:{msg:{}}。validatePropName用于检测key是否合法,Vue中,组件传递参数不能以$开头定义数据。例如$msg就是不合法的。
function normalizePropsOptions(comp, appContext, asMixin = false) { //省略第一部分的代码... //如果没有props且没有全局的mixins //组件本身的mixins、extends则设置 //当前实例的props缓存为空 if (!raw && !hasExtends) { if (shared.isObject(comp)) { cache.set(comp, shared.EMPTY_ARR); } return shared.EMPTY_ARR; } //处理这种类型props:['msg','hello'] if (shared.isArray(raw)) { for (let i = 0; i < raw.length; i++) { if (!shared.isString(raw[i])) { console.warn( `props must be strings when using array syntax. ${raw[i]}` ); } //将v-data-xxx转化为驼峰式vDataXxx //但是不能以$开头 const normalizedKey = shared.camelize(raw[i]); if (validatePropName(normalizedKey)) { //将其变为props:{"msg":{}} normalized[normalizedKey] = shared.EMPTY_OBJ; } } } //省略第三部分的代码... } - 在上面的代码中频繁出现
needCastKeys,这个代表的是需要特殊处理的key,例如:props:{msg:{default:"msg"}}含有default,那么理论上我们应当判断传递的属性值是否存在,然后在决定是否使用default的值,但是这里我们仅进行标准化,所以对于含有default属性的我们需要单独放入needCastKeys中,便于后面对props中的处理。再比如说传递了yes属性,在propsOptions中props:{yes:{type:Boolean}}这样的key=>"yes"也是需要处理的,yes的值应该为true,所以对于type中含有Boolean的也需要放入needCastKeys中。
export function normalizePropsOptions(comp, appContext, asMixin = false) { //省略第二部分代码... if (shared.isArray(raw)) { //省略... } //处理props:{msg:String} else if (raw) { if (!shared.isObject(raw)) { warn(`invalid props options`, raw); } //循环遍历所有的key for (const key in raw) { //"v-data-xxx"=>"vDataXxx"变为小驼峰式 const normalizedKey = shared.camelize(key); //检验key是否合法 if (validatePropName(normalizedKey)) { const opt = raw[key]; //获取value //如果获取的value是数组或函数转化则为{type:opt} //props:{"msg":[]||function(){}}=> //props:{"msg":{type:msg的值}} const prop = (normalized[normalizedKey] = shared.isArray(opt) || shared.isFunction(opt) ? { type: opt } : opt); if (prop) { //找到Boolean在prop.type中的位置 Boolean,["Boolean"] const booleanIndex = getTypeIndex(Boolean, prop.type); //找到String在prop.type中的位置 const stringIndex = getTypeIndex(String, prop.type); prop[0] = booleanIndex > -1; //type中是否包含Boolean //type中不包含String或
相关内容
- Nodejs读取本地json文件,输出json数据接口方式_node.js_
- Vue数据变化后页面更新详细介绍_vue.js_
- vue实现input文本框只能输入0-99的正整数问题_vue.js_
- 微信小程序实现人脸识别对比_javascript技巧_
- Ant Design of Vue的树形控件Tree的使用及说明_vue.js_
- Ant Design Vue 修改表格头部样式的详细代码_vue.js_
- 关于echarts 清空上一次加载的数据问题_vue.js_
- echarts3如何清空上一次加载的series数据_vue.js_
- 使用Element时默认勾选表格toggleRowSelection方式_vue.js_
- 解决获取数据后this.$refs.xxx.toggleRowSelection无效的问题_vue.js_
点击排行
本栏推荐
